Merge branch 'mam-update' into 'master'
MAM Update See merge request poezio/slixmpp!149
This commit is contained in:
commit
7c86c43fc7
@ -92,6 +92,5 @@ Plugin index
|
|||||||
xep_0428
|
xep_0428
|
||||||
xep_0437
|
xep_0437
|
||||||
xep_0439
|
xep_0439
|
||||||
|
xep_0441
|
||||||
xep_0444
|
xep_0444
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,5 +14,6 @@ Stanza elements
|
|||||||
|
|
||||||
.. automodule:: slixmpp.plugins.xep_0313.stanza
|
.. automodule:: slixmpp.plugins.xep_0313.stanza
|
||||||
:members:
|
:members:
|
||||||
|
:member-order: bysource
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
|
18
docs/api/plugins/xep_0441.rst
Normal file
18
docs/api/plugins/xep_0441.rst
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
XEP-0441: Message Archive Management Preferences
|
||||||
|
================================================
|
||||||
|
|
||||||
|
.. module:: slixmpp.plugins.xep_0441
|
||||||
|
|
||||||
|
.. autoclass:: XEP_0441
|
||||||
|
:members:
|
||||||
|
:exclude-members: session_bind, plugin_init, plugin_end
|
||||||
|
|
||||||
|
|
||||||
|
Stanza elements
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. automodule:: slixmpp.plugins.xep_0441.stanza
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
|
@ -22,11 +22,14 @@ class TestMAM(SlixIntegration):
|
|||||||
"""Make sure we can get messages from our archive"""
|
"""Make sure we can get messages from our archive"""
|
||||||
# send messages first
|
# send messages first
|
||||||
tok = randint(1, 999999)
|
tok = randint(1, 999999)
|
||||||
self.clients[0].make_message(mto=self.clients[1].boundjid, mbody='coucou').send()
|
self.clients[0].make_message(
|
||||||
|
mto=self.clients[1].boundjid,
|
||||||
|
mbody=f'coucou {tok}'
|
||||||
|
).send()
|
||||||
await self.clients[1].wait_until('message')
|
await self.clients[1].wait_until('message')
|
||||||
self.clients[1].make_message(
|
self.clients[1].make_message(
|
||||||
mto=self.clients[0].boundjid,
|
mto=self.clients[0].boundjid,
|
||||||
mbody='coucou coucou %s' % tok,
|
mbody=f'coucou coucou {tok}',
|
||||||
).send()
|
).send()
|
||||||
await self.clients[0].wait_until('message')
|
await self.clients[0].wait_until('message')
|
||||||
|
|
||||||
@ -48,8 +51,42 @@ class TestMAM(SlixIntegration):
|
|||||||
if count >= 2:
|
if count >= 2:
|
||||||
break
|
break
|
||||||
|
|
||||||
self.assertEqual(msgs[0]['body'], 'coucou')
|
self.assertEqual(msgs[0]['body'], f'coucou {tok}')
|
||||||
self.assertEqual(msgs[1]['body'], 'coucou coucou %s' % tok)
|
self.assertEqual(msgs[1]['body'], f'coucou coucou {tok}')
|
||||||
|
|
||||||
|
async def test_mam_iterate(self):
|
||||||
|
"""Make sure we can iterate over messages from our archive"""
|
||||||
|
# send messages first
|
||||||
|
tok = randint(1, 999999)
|
||||||
|
self.clients[0].make_message(
|
||||||
|
mto=self.clients[1].boundjid,
|
||||||
|
mbody=f'coucou {tok}'
|
||||||
|
).send()
|
||||||
|
await self.clients[1].wait_until('message')
|
||||||
|
self.clients[1].make_message(
|
||||||
|
mto=self.clients[0].boundjid,
|
||||||
|
mbody='coucou coucou %s' % tok,
|
||||||
|
).send()
|
||||||
|
await self.clients[0].wait_until('message')
|
||||||
|
|
||||||
|
# Get archive
|
||||||
|
retrieve = self.clients[0]['xep_0313'].iterate(
|
||||||
|
with_jid=JID(self.envjid('CI_ACCOUNT2')),
|
||||||
|
reverse=True,
|
||||||
|
rsm={'max': 1}
|
||||||
|
)
|
||||||
|
msgs = []
|
||||||
|
count = 0
|
||||||
|
async for msg in retrieve:
|
||||||
|
msgs.append(
|
||||||
|
msg['mam_result']['forwarded']['stanza']
|
||||||
|
)
|
||||||
|
count += 1
|
||||||
|
if count >= 2:
|
||||||
|
break
|
||||||
|
|
||||||
|
self.assertEqual(msgs[0]['body'], f'coucou coucou {tok}')
|
||||||
|
self.assertEqual(msgs[1]['body'], f'coucou {tok}')
|
||||||
|
|
||||||
|
|
||||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestMAM)
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestMAM)
|
||||||
|
@ -110,5 +110,6 @@ __all__ = [
|
|||||||
'xep_0428', # Message Fallback
|
'xep_0428', # Message Fallback
|
||||||
'xep_0437', # Room Activity Indicators
|
'xep_0437', # Room Activity Indicators
|
||||||
'xep_0439', # Quick Response
|
'xep_0439', # Quick Response
|
||||||
|
'xep_0441', # Message Archive Management Preferences
|
||||||
'xep_0444', # Message Reactions
|
'xep_0444', # Message Reactions
|
||||||
]
|
]
|
||||||
|
@ -135,6 +135,9 @@ class ResultIterator(AsyncIterator):
|
|||||||
not r[self.recv_interface]['rsm']['last']:
|
not r[self.recv_interface]['rsm']['last']:
|
||||||
raise StopAsyncIteration
|
raise StopAsyncIteration
|
||||||
|
|
||||||
|
if self.post_cb:
|
||||||
|
self.post_cb(r)
|
||||||
|
|
||||||
if r[self.recv_interface]['rsm']['count'] and \
|
if r[self.recv_interface]['rsm']['count'] and \
|
||||||
r[self.recv_interface]['rsm']['first_index']:
|
r[self.recv_interface]['rsm']['first_index']:
|
||||||
count = int(r[self.recv_interface]['rsm']['count'])
|
count = int(r[self.recv_interface]['rsm']['count'])
|
||||||
@ -147,9 +150,6 @@ class ResultIterator(AsyncIterator):
|
|||||||
self.start = r[self.recv_interface]['rsm']['first']
|
self.start = r[self.recv_interface]['rsm']['first']
|
||||||
else:
|
else:
|
||||||
self.start = r[self.recv_interface]['rsm']['last']
|
self.start = r[self.recv_interface]['rsm']['last']
|
||||||
|
|
||||||
if self.post_cb:
|
|
||||||
self.post_cb(r)
|
|
||||||
return r
|
return r
|
||||||
except XMPPError:
|
except XMPPError:
|
||||||
raise StopAsyncIteration
|
raise StopAsyncIteration
|
||||||
|
@ -5,8 +5,10 @@
|
|||||||
# See the file LICENSE for copying permissio
|
# See the file LICENSE for copying permissio
|
||||||
from slixmpp.plugins.base import register_plugin
|
from slixmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
from slixmpp.plugins.xep_0313.stanza import Result, MAM, Preferences
|
from slixmpp.plugins.xep_0313.stanza import Result, MAM, Metadata
|
||||||
from slixmpp.plugins.xep_0313.mam import XEP_0313
|
from slixmpp.plugins.xep_0313.mam import XEP_0313
|
||||||
|
|
||||||
|
|
||||||
register_plugin(XEP_0313)
|
register_plugin(XEP_0313)
|
||||||
|
|
||||||
|
__all__ = ['XEP_0313', 'Result', 'MAM', 'Metadata']
|
||||||
|
@ -5,8 +5,17 @@
|
|||||||
# See the file LICENSE for copying permission
|
# See the file LICENSE for copying permission
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from asyncio import Future
|
||||||
|
from collections.abc import AsyncGenerator
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Dict, Callable, Optional, Awaitable
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Awaitable,
|
||||||
|
Callable,
|
||||||
|
Dict,
|
||||||
|
Optional,
|
||||||
|
Tuple,
|
||||||
|
)
|
||||||
|
|
||||||
from slixmpp import JID
|
from slixmpp import JID
|
||||||
from slixmpp.stanza import Message, Iq
|
from slixmpp.stanza import Message, Iq
|
||||||
@ -15,6 +24,7 @@ from slixmpp.xmlstream.matcher import MatchXMLMask
|
|||||||
from slixmpp.xmlstream import register_stanza_plugin
|
from slixmpp.xmlstream import register_stanza_plugin
|
||||||
from slixmpp.plugins import BasePlugin
|
from slixmpp.plugins import BasePlugin
|
||||||
from slixmpp.plugins.xep_0313 import stanza
|
from slixmpp.plugins.xep_0313 import stanza
|
||||||
|
from slixmpp.plugins.xep_0004.stanza import Form
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -28,17 +38,25 @@ class XEP_0313(BasePlugin):
|
|||||||
|
|
||||||
name = 'xep_0313'
|
name = 'xep_0313'
|
||||||
description = 'XEP-0313: Message Archive Management'
|
description = 'XEP-0313: Message Archive Management'
|
||||||
dependencies = {'xep_0030', 'xep_0050', 'xep_0059', 'xep_0297'}
|
dependencies = {
|
||||||
|
'xep_0004', 'xep_0030', 'xep_0050', 'xep_0059', 'xep_0297'
|
||||||
|
}
|
||||||
stanza = stanza
|
stanza = stanza
|
||||||
|
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(stanza.MAM, Form)
|
||||||
register_stanza_plugin(Iq, stanza.MAM)
|
register_stanza_plugin(Iq, stanza.MAM)
|
||||||
register_stanza_plugin(Iq, stanza.Preferences)
|
|
||||||
register_stanza_plugin(Message, stanza.Result)
|
register_stanza_plugin(Message, stanza.Result)
|
||||||
register_stanza_plugin(Iq, stanza.Fin)
|
register_stanza_plugin(Iq, stanza.Fin)
|
||||||
register_stanza_plugin(stanza.Result, self.xmpp['xep_0297'].stanza.Forwarded)
|
register_stanza_plugin(
|
||||||
|
stanza.Result,
|
||||||
|
self.xmpp['xep_0297'].stanza.Forwarded
|
||||||
|
)
|
||||||
register_stanza_plugin(stanza.MAM, self.xmpp['xep_0059'].stanza.Set)
|
register_stanza_plugin(stanza.MAM, self.xmpp['xep_0059'].stanza.Set)
|
||||||
register_stanza_plugin(stanza.Fin, self.xmpp['xep_0059'].stanza.Set)
|
register_stanza_plugin(stanza.Fin, self.xmpp['xep_0059'].stanza.Set)
|
||||||
|
register_stanza_plugin(Iq, stanza.Metadata)
|
||||||
|
register_stanza_plugin(stanza.Metadata, stanza.Start)
|
||||||
|
register_stanza_plugin(stanza.Metadata, stanza.End)
|
||||||
|
|
||||||
def retrieve(
|
def retrieve(
|
||||||
self,
|
self,
|
||||||
@ -66,16 +84,10 @@ class XEP_0313(BasePlugin):
|
|||||||
:param bool iterator: Use RSM and iterate over a paginated query
|
:param bool iterator: Use RSM and iterate over a paginated query
|
||||||
:param dict rsm: RSM custom options
|
:param dict rsm: RSM custom options
|
||||||
"""
|
"""
|
||||||
iq = self.xmpp.Iq()
|
iq, stanza_mask = self._pre_mam_retrieve(
|
||||||
|
jid, start, end, with_jid, ifrom
|
||||||
|
)
|
||||||
query_id = iq['id']
|
query_id = iq['id']
|
||||||
|
|
||||||
iq['to'] = jid
|
|
||||||
iq['from'] = ifrom
|
|
||||||
iq['type'] = 'set'
|
|
||||||
iq['mam']['queryid'] = query_id
|
|
||||||
iq['mam']['start'] = start
|
|
||||||
iq['mam']['end'] = end
|
|
||||||
iq['mam']['with'] = with_jid
|
|
||||||
amount = 10
|
amount = 10
|
||||||
if rsm:
|
if rsm:
|
||||||
for key, value in rsm.items():
|
for key, value in rsm.items():
|
||||||
@ -84,12 +96,6 @@ class XEP_0313(BasePlugin):
|
|||||||
amount = value
|
amount = value
|
||||||
cb_data = {}
|
cb_data = {}
|
||||||
|
|
||||||
stanza_mask = self.xmpp.Message()
|
|
||||||
stanza_mask.xml.remove(stanza_mask.xml.find('{urn:xmpp:sid:0}origin-id'))
|
|
||||||
del stanza_mask['id']
|
|
||||||
del stanza_mask['lang']
|
|
||||||
stanza_mask['from'] = jid
|
|
||||||
stanza_mask['mam_result']['queryid'] = query_id
|
|
||||||
xml_mask = str(stanza_mask)
|
xml_mask = str(stanza_mask)
|
||||||
|
|
||||||
def pre_cb(query: Iq) -> None:
|
def pre_cb(query: Iq) -> None:
|
||||||
@ -106,11 +112,14 @@ class XEP_0313(BasePlugin):
|
|||||||
results = cb_data['collector'].stop()
|
results = cb_data['collector'].stop()
|
||||||
if result['type'] == 'result':
|
if result['type'] == 'result':
|
||||||
result['mam']['results'] = results
|
result['mam']['results'] = results
|
||||||
|
result['mam_fin']['results'] = results
|
||||||
|
|
||||||
if iterator:
|
if iterator:
|
||||||
return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results', amount=amount,
|
return self.xmpp['xep_0059'].iterate(
|
||||||
reverse=reverse, recv_interface='mam_fin',
|
iq, 'mam', 'results', amount=amount,
|
||||||
pre_cb=pre_cb, post_cb=post_cb)
|
reverse=reverse, recv_interface='mam_fin',
|
||||||
|
pre_cb=pre_cb, post_cb=post_cb
|
||||||
|
)
|
||||||
|
|
||||||
collector = Collector(
|
collector = Collector(
|
||||||
'MAM_Results_%s' % query_id,
|
'MAM_Results_%s' % query_id,
|
||||||
@ -126,26 +135,144 @@ class XEP_0313(BasePlugin):
|
|||||||
|
|
||||||
return iq.send(timeout=timeout, callback=wrapped_cb)
|
return iq.send(timeout=timeout, callback=wrapped_cb)
|
||||||
|
|
||||||
def get_preferences(self, timeout=None, callback=None):
|
async def iterate(
|
||||||
iq = self.xmpp.Iq()
|
self,
|
||||||
iq['type'] = 'get'
|
jid: Optional[JID] = None,
|
||||||
|
start: Optional[datetime] = None,
|
||||||
|
end: Optional[datetime] = None,
|
||||||
|
with_jid: Optional[JID] = None,
|
||||||
|
ifrom: Optional[JID] = None,
|
||||||
|
reverse: bool = False,
|
||||||
|
rsm: Optional[Dict[str, Any]] = None,
|
||||||
|
total: Optional[int] = None,
|
||||||
|
) -> AsyncGenerator:
|
||||||
|
"""
|
||||||
|
Iterate over each message of MAM query.
|
||||||
|
|
||||||
|
:param jid: Entity holding the MAM records
|
||||||
|
:param start: MAM query start time
|
||||||
|
:param end: MAM query end time
|
||||||
|
:param with_jid: Filter results on this JID
|
||||||
|
:param ifrom: To change the from address of the query
|
||||||
|
:param reverse: Get the results in reverse order
|
||||||
|
:param rsm: RSM custom options
|
||||||
|
:param total: A number of messages received after which the query
|
||||||
|
should stop.
|
||||||
|
"""
|
||||||
|
iq, stanza_mask = self._pre_mam_retrieve(
|
||||||
|
jid, start, end, with_jid, ifrom
|
||||||
|
)
|
||||||
query_id = iq['id']
|
query_id = iq['id']
|
||||||
iq['mam_prefs']['query_id'] = query_id
|
amount = 10
|
||||||
return iq.send(timeout=timeout, callback=callback)
|
|
||||||
|
|
||||||
def set_preferences(self, jid=None, default=None, always=None, never=None,
|
if rsm:
|
||||||
ifrom=None, timeout=None, callback=None):
|
for key, value in rsm.items():
|
||||||
iq = self.xmpp.Iq()
|
iq['mam']['rsm'][key] = str(value)
|
||||||
iq['type'] = 'set'
|
if key == 'max':
|
||||||
iq['to'] = jid
|
amount = value
|
||||||
iq['from'] = ifrom
|
cb_data = {}
|
||||||
iq['mam_prefs']['default'] = default
|
|
||||||
iq['mam_prefs']['always'] = always
|
|
||||||
iq['mam_prefs']['never'] = never
|
|
||||||
return iq.send(timeout=timeout, callback=callback)
|
|
||||||
|
|
||||||
def get_configuration_commands(self, jid, **kwargs):
|
def pre_cb(query: Iq) -> None:
|
||||||
return self.xmpp['xep_0030'].get_items(
|
stanza_mask['mam_result']['queryid'] = query['id']
|
||||||
jid=jid,
|
xml_mask = str(stanza_mask)
|
||||||
node='urn:xmpp:mam#configure',
|
query['mam']['queryid'] = query['id']
|
||||||
**kwargs)
|
collector = Collector(
|
||||||
|
'MAM_Results_%s' % query_id,
|
||||||
|
MatchXMLMask(xml_mask))
|
||||||
|
self.xmpp.register_handler(collector)
|
||||||
|
cb_data['collector'] = collector
|
||||||
|
|
||||||
|
def post_cb(result: Iq) -> None:
|
||||||
|
results = cb_data['collector'].stop()
|
||||||
|
if result['type'] == 'result':
|
||||||
|
result['mam']['results'] = results
|
||||||
|
result['mam_fin']['results'] = results
|
||||||
|
|
||||||
|
iterator = self.xmpp['xep_0059'].iterate(
|
||||||
|
iq, 'mam', 'results', amount=amount,
|
||||||
|
reverse=reverse, recv_interface='mam_fin',
|
||||||
|
pre_cb=pre_cb, post_cb=post_cb
|
||||||
|
)
|
||||||
|
recv_count = 0
|
||||||
|
|
||||||
|
async for page in iterator:
|
||||||
|
messages = [message for message in page['mam']['results']]
|
||||||
|
if reverse:
|
||||||
|
messages.reverse()
|
||||||
|
for message in messages:
|
||||||
|
yield message
|
||||||
|
recv_count += 1
|
||||||
|
if total is not None and recv_count >= total:
|
||||||
|
break
|
||||||
|
if total is not None and recv_count >= total:
|
||||||
|
break
|
||||||
|
|
||||||
|
def _pre_mam_retrieve(
|
||||||
|
self,
|
||||||
|
jid: Optional[JID] = None,
|
||||||
|
start: Optional[datetime] = None,
|
||||||
|
end: Optional[datetime] = None,
|
||||||
|
with_jid: Optional[JID] = None,
|
||||||
|
ifrom: Optional[JID] = None,
|
||||||
|
) -> Tuple[Iq, Message]:
|
||||||
|
"""Build the IQ and stanza mask for MAM results
|
||||||
|
"""
|
||||||
|
iq = self.xmpp.make_iq_set(ito=jid, ifrom=ifrom)
|
||||||
|
query_id = iq['id']
|
||||||
|
iq['mam']['queryid'] = query_id
|
||||||
|
iq['mam']['start'] = start
|
||||||
|
iq['mam']['end'] = end
|
||||||
|
iq['mam']['with'] = with_jid
|
||||||
|
|
||||||
|
stanza_mask = self.xmpp.Message()
|
||||||
|
|
||||||
|
auto_origin = stanza_mask.xml.find('{urn:xmpp:sid:0}origin-id')
|
||||||
|
if auto_origin is not None:
|
||||||
|
stanza_mask.xml.remove(auto_origin)
|
||||||
|
del stanza_mask['id']
|
||||||
|
del stanza_mask['lang']
|
||||||
|
stanza_mask['from'] = jid
|
||||||
|
stanza_mask['mam_result']['queryid'] = query_id
|
||||||
|
|
||||||
|
return (iq, stanza_mask)
|
||||||
|
|
||||||
|
async def get_fields(self, jid: Optional[JID] = None, **iqkwargs) -> Form:
|
||||||
|
"""Get MAM query fields.
|
||||||
|
|
||||||
|
.. versionaddedd:: 1.8.0
|
||||||
|
|
||||||
|
:param jid: JID to retrieve the policy from.
|
||||||
|
:return: The Form of allowed options
|
||||||
|
"""
|
||||||
|
ifrom = iqkwargs.pop('ifrom', None)
|
||||||
|
iq = self.xmpp.make_iq_get(ito=jid, ifrom=ifrom)
|
||||||
|
iq.enable('mam')
|
||||||
|
result = await iq.send(**iqkwargs)
|
||||||
|
return result['mam']['form']
|
||||||
|
|
||||||
|
async def get_configuration_commands(self, jid: Optional[JID],
|
||||||
|
**discokwargs) -> Future:
|
||||||
|
"""Get the list of MAM advanced configuration commands.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.8.0
|
||||||
|
|
||||||
|
:param jid: JID to get the commands from.
|
||||||
|
"""
|
||||||
|
if jid is None:
|
||||||
|
jid = self.xmpp.boundjid.bare
|
||||||
|
return await self.xmpp['xep_0030'].get_items(
|
||||||
|
jid=jid,
|
||||||
|
node='urn:xmpp:mam#configure',
|
||||||
|
**discokwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_archive_metadata(self, jid: Optional[JID] = None,
|
||||||
|
**iqkwargs) -> Future:
|
||||||
|
"""Get the archive metadata from a JID.
|
||||||
|
|
||||||
|
:param jid: JID to get the metadata from.
|
||||||
|
"""
|
||||||
|
ifrom = iqkwargs.pop('ifrom', None)
|
||||||
|
iq = self.xmpp.make_iq_get(ito=jid, ifrom=ifrom)
|
||||||
|
iq.enable('mam_metadata')
|
||||||
|
return iq.send(**iqkwargs)
|
||||||
|
@ -1,159 +1,342 @@
|
|||||||
|
|
||||||
# Slixmpp: The Slick XMPP Library
|
# Slixmpp: The Slick XMPP Library
|
||||||
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
# This file is part of Slixmpp.
|
# This file is part of Slixmpp.
|
||||||
# See the file LICENSE for copying permissio
|
# 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.jid import JID
|
||||||
from slixmpp.xmlstream import ElementBase, ET
|
from slixmpp.xmlstream import ElementBase, ET
|
||||||
from slixmpp.plugins import xep_0082, xep_0004
|
from slixmpp.plugins import xep_0082
|
||||||
|
|
||||||
|
|
||||||
class MAM(ElementBase):
|
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'
|
name = 'query'
|
||||||
namespace = 'urn:xmpp:mam:2'
|
namespace = 'urn:xmpp:mam:2'
|
||||||
plugin_attrib = 'mam'
|
plugin_attrib = 'mam'
|
||||||
interfaces = {'queryid', 'start', 'end', 'with', 'results'}
|
#: Available interfaces:
|
||||||
sub_interfaces = {'start', 'end', 'with'}
|
#:
|
||||||
|
#: - ``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):
|
def setup(self, xml=None):
|
||||||
ElementBase.setup(self, xml)
|
ElementBase.setup(self, xml)
|
||||||
self._form = xep_0004.stanza.Form()
|
self._results: List[Message] = []
|
||||||
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):
|
def _setup_form(self):
|
||||||
return self._form.get_fields()
|
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_start(self):
|
def get_fields(self):
|
||||||
fields = self.__get_fields()
|
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')
|
field = fields.get('start')
|
||||||
if field:
|
if field:
|
||||||
return xep_0082.parse(field['value'])
|
return xep_0082.parse(field['value'])
|
||||||
|
return None
|
||||||
|
|
||||||
def set_start(self, value):
|
def set_start(self, value: Union[str, datetime]):
|
||||||
if isinstance(value, dt.datetime):
|
self._setup_form()
|
||||||
|
if isinstance(value, datetime):
|
||||||
value = xep_0082.format_datetime(value)
|
value = xep_0082.format_datetime(value)
|
||||||
fields = self.__get_fields()
|
self.set_custom_field('start', value)
|
||||||
field = fields.get('start')
|
|
||||||
if field:
|
|
||||||
field['value'] = value
|
|
||||||
else:
|
|
||||||
field = self._form.add_field(var='start')
|
|
||||||
field['value'] = value
|
|
||||||
|
|
||||||
def get_end(self):
|
def get_end(self) -> Optional[datetime]:
|
||||||
fields = self.__get_fields()
|
fields = self.get_fields()
|
||||||
field = fields.get('end')
|
field = fields.get('end')
|
||||||
if field:
|
if field:
|
||||||
return xep_0082.parse(field['value'])
|
return xep_0082.parse(field['value'])
|
||||||
|
return None
|
||||||
|
|
||||||
def set_end(self, value):
|
def set_end(self, value: Union[str, datetime]):
|
||||||
if isinstance(value, dt.datetime):
|
if isinstance(value, datetime):
|
||||||
value = xep_0082.format_datetime(value)
|
value = xep_0082.format_datetime(value)
|
||||||
fields = self.__get_fields()
|
self.set_custom_field('end', value)
|
||||||
field = fields.get('end')
|
|
||||||
if field:
|
|
||||||
field['value'] = value
|
|
||||||
else:
|
|
||||||
field = self._form.add_field(var='end')
|
|
||||||
field['value'] = value
|
|
||||||
|
|
||||||
def get_with(self):
|
def get_with(self) -> Optional[JID]:
|
||||||
fields = self.__get_fields()
|
fields = self.get_fields()
|
||||||
field = fields.get('with')
|
field = fields.get('with')
|
||||||
if field:
|
if field:
|
||||||
return JID(field['value'])
|
return JID(field['value'])
|
||||||
|
return None
|
||||||
|
|
||||||
def set_with(self, value):
|
def set_with(self, value: JID):
|
||||||
fields = self.__get_fields()
|
self.set_custom_field('with', value)
|
||||||
field = fields.get('with')
|
|
||||||
|
def set_custom_field(self, fieldname: str, value: Any):
|
||||||
|
self._setup_form()
|
||||||
|
fields = self.get_fields()
|
||||||
|
field = fields.get(fieldname)
|
||||||
if field:
|
if field:
|
||||||
field['with'] = str(value)
|
|
||||||
else:
|
|
||||||
field = self._form.add_field(var='with')
|
|
||||||
field['value'] = str(value)
|
field['value'] = str(value)
|
||||||
|
else:
|
||||||
|
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
|
# The results interface is meant only as an easy
|
||||||
# way to access the set of collected message responses
|
# way to access the set of collected message responses
|
||||||
# from the query.
|
# from the query.
|
||||||
|
|
||||||
def get_results(self):
|
def get_results(self) -> List[Message]:
|
||||||
return self._results
|
return self._results
|
||||||
|
|
||||||
def set_results(self, values):
|
def set_results(self, values: List[Message]):
|
||||||
self._results = values
|
self._results = values
|
||||||
|
|
||||||
def del_results(self):
|
def del_results(self):
|
||||||
self._results = []
|
self._results = []
|
||||||
|
|
||||||
|
|
||||||
class Preferences(ElementBase):
|
|
||||||
name = 'prefs'
|
|
||||||
namespace = 'urn:xmpp:mam:2'
|
|
||||||
plugin_attrib = 'mam_prefs'
|
|
||||||
interfaces = {'default', 'always', 'never'}
|
|
||||||
sub_interfaces = {'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 Fin(ElementBase):
|
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'
|
name = 'fin'
|
||||||
namespace = 'urn:xmpp:mam:2'
|
namespace = 'urn:xmpp:mam:2'
|
||||||
plugin_attrib = 'mam_fin'
|
plugin_attrib = 'mam_fin'
|
||||||
|
interfaces = {'results'}
|
||||||
|
|
||||||
|
def setup(self, xml=None):
|
||||||
|
ElementBase.setup(self, xml)
|
||||||
|
self._results: List[Message] = []
|
||||||
|
|
||||||
|
# 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) -> List[Message]:
|
||||||
|
return self._results
|
||||||
|
|
||||||
|
def set_results(self, values: List[Message]):
|
||||||
|
self._results = values
|
||||||
|
|
||||||
|
def del_results(self):
|
||||||
|
self._results = []
|
||||||
|
|
||||||
|
|
||||||
class Result(ElementBase):
|
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'
|
name = 'result'
|
||||||
namespace = 'urn:xmpp:mam:2'
|
namespace = 'urn:xmpp:mam:2'
|
||||||
plugin_attrib = 'mam_result'
|
plugin_attrib = 'mam_result'
|
||||||
|
#: Available interfaces:
|
||||||
|
#:
|
||||||
|
#: - ``queryid``: MAM queryid
|
||||||
|
#: - ``id``: ID of the result
|
||||||
interfaces = {'queryid', 'id'}
|
interfaces = {'queryid', 'id'}
|
||||||
|
|
||||||
|
|
||||||
|
class Metadata(ElementBase):
|
||||||
|
"""Element containing archive metadata
|
||||||
|
|
||||||
|
.. 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 = 'metadata'
|
||||||
|
namespace = 'urn:xmpp:mam:2'
|
||||||
|
plugin_attrib = 'mam_metadata'
|
||||||
|
|
||||||
|
|
||||||
|
class Start(ElementBase):
|
||||||
|
"""Metadata about the start 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 = 'start'
|
||||||
|
namespace = 'urn:xmpp:mam:2'
|
||||||
|
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 = 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
|
||||||
|
13
slixmpp/plugins/xep_0441/__init__.py
Normal file
13
slixmpp/plugins/xep_0441/__init__.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Slixmpp: The Slick XMPP Library
|
||||||
|
# Copyright (C) 2021 Mathieu Pasquet
|
||||||
|
# This file is part of Slixmpp.
|
||||||
|
# See the file LICENSE for copying permissio
|
||||||
|
from slixmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from slixmpp.plugins.xep_0441.stanza import Preferences
|
||||||
|
from slixmpp.plugins.xep_0441.mam_prefs import XEP_0441
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0441)
|
||||||
|
|
||||||
|
__all__ = ['XEP_0441', 'Preferences']
|
75
slixmpp/plugins/xep_0441/mam_prefs.py
Normal file
75
slixmpp/plugins/xep_0441/mam_prefs.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# Slixmpp: The Slick XMPP Library
|
||||||
|
# Copyright (C) 2021 Mathieu Pasquet
|
||||||
|
# This file is part of Slixmpp.
|
||||||
|
# See the file LICENSE for copying permission
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from asyncio import Future
|
||||||
|
from typing import (
|
||||||
|
List,
|
||||||
|
Optional,
|
||||||
|
Tuple,
|
||||||
|
)
|
||||||
|
|
||||||
|
from slixmpp import JID
|
||||||
|
from slixmpp.types import MAMDefault
|
||||||
|
from slixmpp.stanza import Iq
|
||||||
|
from slixmpp.xmlstream import register_stanza_plugin
|
||||||
|
from slixmpp.plugins import BasePlugin
|
||||||
|
from slixmpp.plugins.xep_0441 import stanza
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0441(BasePlugin):
|
||||||
|
|
||||||
|
"""
|
||||||
|
XEP-0441: Message Archive Management Preferences
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'xep_0441'
|
||||||
|
description = 'XEP-0441: Message Archive Management Preferences'
|
||||||
|
dependencies = {'xep_0313'}
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Iq, stanza.Preferences)
|
||||||
|
|
||||||
|
async def get_preferences(self, **iqkwargs
|
||||||
|
) -> Tuple[MAMDefault, List[JID], List[JID]]:
|
||||||
|
"""Get the current MAM preferences.
|
||||||
|
|
||||||
|
:returns: A tuple of MAM preferences with (default, always, never)
|
||||||
|
"""
|
||||||
|
ifrom = iqkwargs.pop('ifrom', None)
|
||||||
|
ito = iqkwargs.pop('ito', None)
|
||||||
|
iq = self.xmpp.make_iq_get(ito=ito, ifrom=ifrom)
|
||||||
|
iq['type'] = 'get'
|
||||||
|
query_id = iq['id']
|
||||||
|
iq['mam_prefs']['query_id'] = query_id
|
||||||
|
result = await iq.send(**iqkwargs)
|
||||||
|
return (
|
||||||
|
result['mam_prefs']['default'],
|
||||||
|
result['mam_prefs']['always'],
|
||||||
|
result['mam_prefs']['never']
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_preferences(self, default: Optional[MAMDefault] = 'roster',
|
||||||
|
always: Optional[List[JID]] = None,
|
||||||
|
never: Optional[List[JID]] = None, *,
|
||||||
|
ito: Optional[JID] = None, ifrom: Optional[JID] = None,
|
||||||
|
**iqkwargs) -> Future:
|
||||||
|
"""Set MAM Preferences.
|
||||||
|
|
||||||
|
The server answer MAY contain different items.
|
||||||
|
|
||||||
|
:param default: Default behavior (one of 'always', 'never', 'roster').
|
||||||
|
:param always: List of JIDs whose messages will always be stored.
|
||||||
|
:param never: List of JIDs whose messages will never be stored.
|
||||||
|
"""
|
||||||
|
iq = self.xmpp.make_iq_set(ito=ito, ifrom=ifrom)
|
||||||
|
iq['mam_prefs']['default'] = default
|
||||||
|
iq['mam_prefs']['always'] = always
|
||||||
|
iq['mam_prefs']['never'] = never
|
||||||
|
return iq.send(**iqkwargs)
|
91
slixmpp/plugins/xep_0441/stanza.py
Normal file
91
slixmpp/plugins/xep_0441/stanza.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
# Slixmpp: The Slick XMPP Library
|
||||||
|
# Copyright (C) 2021 Mathieu Pasquet
|
||||||
|
# This file is part of Slixmpp.
|
||||||
|
# See the file LICENSE for copying permissio
|
||||||
|
from typing import (
|
||||||
|
Iterable,
|
||||||
|
Set,
|
||||||
|
)
|
||||||
|
|
||||||
|
from slixmpp.jid import JID
|
||||||
|
from slixmpp.xmlstream import ElementBase, ET
|
||||||
|
|
||||||
|
|
||||||
|
class Preferences(ElementBase):
|
||||||
|
"""MAM Preferences payload.
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<iq type='set' id='juliet3'>
|
||||||
|
<prefs xmlns='urn:xmpp:mam:2' default='roster'>
|
||||||
|
<always>
|
||||||
|
<jid>romeo@montague.lit</jid>
|
||||||
|
</always>
|
||||||
|
<never>
|
||||||
|
<jid>montague@montague.lit</jid>
|
||||||
|
</never>
|
||||||
|
</prefs>
|
||||||
|
</iq>
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = 'prefs'
|
||||||
|
namespace = 'urn:xmpp:mam:2'
|
||||||
|
plugin_attrib = 'mam_prefs'
|
||||||
|
#: Available interfaces:
|
||||||
|
#:
|
||||||
|
#: - ``default``: Default MAM policy (must be one of 'roster', 'always',
|
||||||
|
#: 'never'
|
||||||
|
#: - ``always`` (``List[JID]``): list of JIDs to always store
|
||||||
|
#: conversations with.
|
||||||
|
#: - ``never`` (``List[JID]``): list of JIDs to never store
|
||||||
|
#: conversations with.
|
||||||
|
interfaces = {'default', 'always', 'never'}
|
||||||
|
sub_interfaces = {'always', 'never'}
|
||||||
|
|
||||||
|
def get_always(self) -> Set[JID]:
|
||||||
|
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: Iterable[JID]):
|
||||||
|
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) -> Set[JID]:
|
||||||
|
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: Iterable[JID]):
|
||||||
|
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)
|
@ -76,3 +76,5 @@ MucRoomItemKeys = Literal[
|
|||||||
OptJid = Optional[JID]
|
OptJid = Optional[JID]
|
||||||
JidStr = Union[str, JID]
|
JidStr = Union[str, JID]
|
||||||
OptJidStr = Optional[Union[str, JID]]
|
OptJidStr = Optional[Union[str, JID]]
|
||||||
|
|
||||||
|
MAMDefault = Literal['always', 'never', 'roster']
|
||||||
|
105
tests/test_stanza_xep_0313.py
Normal file
105
tests/test_stanza_xep_0313.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import unittest
|
||||||
|
from slixmpp import JID, Iq, Message
|
||||||
|
from slixmpp.test import SlixTest
|
||||||
|
from slixmpp.plugins.xep_0313 import stanza
|
||||||
|
from slixmpp.plugins.xep_0004.stanza import Form
|
||||||
|
from slixmpp.plugins.xep_0297 import stanza as fstanza
|
||||||
|
from slixmpp.plugins.xep_0059 import stanza as rstanza
|
||||||
|
from slixmpp.xmlstream import register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class TestMAM(SlixTest):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
register_stanza_plugin(stanza.MAM, Form)
|
||||||
|
register_stanza_plugin(Iq, stanza.MAM)
|
||||||
|
register_stanza_plugin(Message, stanza.Result)
|
||||||
|
register_stanza_plugin(Iq, stanza.Fin)
|
||||||
|
register_stanza_plugin(
|
||||||
|
stanza.Result,
|
||||||
|
fstanza.Forwarded
|
||||||
|
)
|
||||||
|
register_stanza_plugin(stanza.MAM, rstanza.Set)
|
||||||
|
register_stanza_plugin(stanza.Fin, rstanza.Set)
|
||||||
|
|
||||||
|
register_stanza_plugin(Iq, stanza.Metadata)
|
||||||
|
register_stanza_plugin(stanza.Metadata, stanza.Start)
|
||||||
|
register_stanza_plugin(stanza.Metadata, stanza.End)
|
||||||
|
|
||||||
|
def testMAMQuery(self):
|
||||||
|
"""Test that we can build a simple MAM query."""
|
||||||
|
iq = Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['mam']['queryid'] = 'f27'
|
||||||
|
|
||||||
|
self.check(iq, """
|
||||||
|
<iq type='set'>
|
||||||
|
<query xmlns='urn:xmpp:mam:2' queryid='f27'/>
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
def testMAMQueryOptions(self):
|
||||||
|
"""Test that we can build a mam query with all options."""
|
||||||
|
iq = Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['mam']['with'] = JID('juliet@capulet.lit')
|
||||||
|
iq['mam']['start'] = '2010-06-07T00:00:00Z'
|
||||||
|
iq['mam']['end'] = '2010-07-07T13:23:54Z'
|
||||||
|
iq['mam']['after_id'] = 'id1'
|
||||||
|
iq['mam']['before_id'] = 'id2'
|
||||||
|
iq['mam']['ids'] = ['a', 'b', 'c']
|
||||||
|
|
||||||
|
self.check(iq, """
|
||||||
|
<iq type='set'>
|
||||||
|
<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>
|
||||||
|
<field var='start'>
|
||||||
|
<value>2010-06-07T00:00:00Z</value>
|
||||||
|
</field>
|
||||||
|
<field var='end'>
|
||||||
|
<value>2010-07-07T13:23:54Z</value>
|
||||||
|
</field>
|
||||||
|
<field var='after-id'>
|
||||||
|
<value>id1</value>
|
||||||
|
</field>
|
||||||
|
<field var='before-id'>
|
||||||
|
<value>id2</value>
|
||||||
|
</field>
|
||||||
|
<field var='ids'>
|
||||||
|
<value>a</value>
|
||||||
|
<value>b</value>
|
||||||
|
<value>c</value>
|
||||||
|
</field>
|
||||||
|
</x>
|
||||||
|
</query>
|
||||||
|
</iq>
|
||||||
|
""", use_values=False)
|
||||||
|
|
||||||
|
def testMAMMetadata(self):
|
||||||
|
"""Test that we can build a MAM metadata payload"""
|
||||||
|
|
||||||
|
iq = Iq()
|
||||||
|
iq['type'] = 'result'
|
||||||
|
iq['mam_metadata']['start']['id'] = 'YWxwaGEg'
|
||||||
|
iq['mam_metadata']['start']['timestamp'] = '2008-08-22T21:09:04Z'
|
||||||
|
iq['mam_metadata']['end']['id'] = 'b21lZ2Eg'
|
||||||
|
iq['mam_metadata']['end']['timestamp'] = '2020-04-20T14:34:21Z'
|
||||||
|
|
||||||
|
self.check(iq, """
|
||||||
|
<iq type='result'>
|
||||||
|
<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>
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestMAM)
|
340
tests/test_stream_xep_0313.py
Normal file
340
tests/test_stream_xep_0313.py
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
import unittest
|
||||||
|
from datetime import datetime
|
||||||
|
from slixmpp.test import SlixTest
|
||||||
|
from slixmpp import JID
|
||||||
|
|
||||||
|
|
||||||
|
class TestMAM(SlixTest):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.stream_start(plugins=['xep_0313'])
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.stream_close()
|
||||||
|
|
||||||
|
def testRetrieveSimple(self):
|
||||||
|
"""Test requesting MAM messages without RSM"""
|
||||||
|
|
||||||
|
msgs = []
|
||||||
|
|
||||||
|
async def test():
|
||||||
|
iq = await self.xmpp['xep_0313'].retrieve()
|
||||||
|
for message in iq['mam']['results']:
|
||||||
|
msgs.append(message)
|
||||||
|
|
||||||
|
fut = self.xmpp.wrap(test())
|
||||||
|
self.wait_()
|
||||||
|
self.send("""
|
||||||
|
<iq type='set' id='1'>
|
||||||
|
<query xmlns='urn:xmpp:mam:2' queryid='1' />
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.recv("""
|
||||||
|
<message id='abc' to='tester@localhost/resource'>
|
||||||
|
<result xmlns='urn:xmpp:mam:2' queryid='1'
|
||||||
|
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="tester@localhost">
|
||||||
|
<body>Hail to thee</body>
|
||||||
|
</message>
|
||||||
|
</forwarded>
|
||||||
|
</result>
|
||||||
|
</message>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.recv("""
|
||||||
|
<iq type="result" id="1" to="tester@localhost">
|
||||||
|
<fin xmlns="urn:xmpp:mam:2">
|
||||||
|
<first index='0'>28482-98726-73623</first>
|
||||||
|
<last>28482-98726-73623</last>
|
||||||
|
</fin>
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.run_coro(fut)
|
||||||
|
self.assertEqual(
|
||||||
|
msgs[0]['mam_result']['forwarded']['message']['body'],
|
||||||
|
"Hail to thee",
|
||||||
|
)
|
||||||
|
self.assertEqual(len(msgs),1)
|
||||||
|
|
||||||
|
def testRetrieveRSM(self):
|
||||||
|
"""Test requesting MAM messages with RSM"""
|
||||||
|
|
||||||
|
msgs = []
|
||||||
|
|
||||||
|
async def test():
|
||||||
|
iterator = self.xmpp['xep_0313'].retrieve(
|
||||||
|
with_jid=JID('toto@titi'),
|
||||||
|
start='2010-06-07T00:00:00Z',
|
||||||
|
iterator=True,
|
||||||
|
)
|
||||||
|
async for page in iterator:
|
||||||
|
for message in page['mam']['results']:
|
||||||
|
msgs.append(message)
|
||||||
|
|
||||||
|
fut = self.xmpp.wrap(test())
|
||||||
|
self.wait_()
|
||||||
|
self.send("""
|
||||||
|
<iq type='set' id='2'>
|
||||||
|
<query xmlns='urn:xmpp:mam:2' queryid='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>toto@titi</value>
|
||||||
|
</field>
|
||||||
|
<field var='start'>
|
||||||
|
<value>2010-06-07T00:00:00Z</value>
|
||||||
|
</field>
|
||||||
|
</x>
|
||||||
|
<set xmlns="http://jabber.org/protocol/rsm">
|
||||||
|
<max>10</max>
|
||||||
|
</set>
|
||||||
|
</query>
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.recv("""
|
||||||
|
<message id='abc' to='tester@localhost/resource'>
|
||||||
|
<result xmlns='urn:xmpp:mam:2' queryid='2'
|
||||||
|
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="tester@localhost">
|
||||||
|
<body>Hail to thee</body>
|
||||||
|
</message>
|
||||||
|
</forwarded>
|
||||||
|
</result>
|
||||||
|
</message>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.recv("""
|
||||||
|
<iq type="result" id="2" to="tester@localhost">
|
||||||
|
<fin xmlns="urn:xmpp:mam:2">
|
||||||
|
<set xmlns='http://jabber.org/protocol/rsm'>
|
||||||
|
<first index='0'>28482-98726-73623</first>
|
||||||
|
<last>28482-98726-73623</last>
|
||||||
|
<count>2</count>
|
||||||
|
</set>
|
||||||
|
</fin>
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.send("""
|
||||||
|
<iq type='set' id='3'>
|
||||||
|
<query xmlns='urn:xmpp:mam:2' queryid='3'>
|
||||||
|
<x xmlns='jabber:x:data' type='submit'>
|
||||||
|
<field var='FORM_TYPE' type='hidden'>
|
||||||
|
<value>urn:xmpp:mam:2</value>
|
||||||
|
</field>
|
||||||
|
<field var='with'>
|
||||||
|
<value>toto@titi</value>
|
||||||
|
</field>
|
||||||
|
<field var='start'>
|
||||||
|
<value>2010-06-07T00:00:00Z</value>
|
||||||
|
</field>
|
||||||
|
</x>
|
||||||
|
<set xmlns="http://jabber.org/protocol/rsm">
|
||||||
|
<max>10</max>
|
||||||
|
<after>28482-98726-73623</after>
|
||||||
|
</set>
|
||||||
|
</query>
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.recv("""
|
||||||
|
<message id='abc' to='tester@localhost/resource'>
|
||||||
|
<result xmlns='urn:xmpp:mam:2' queryid='3'
|
||||||
|
id='28482-98726-73624'>
|
||||||
|
<forwarded xmlns='urn:xmpp:forward:0'>
|
||||||
|
<delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:26Z'/>
|
||||||
|
<message xmlns='jabber:client' from="witch@shakespeare.lit"
|
||||||
|
to="tester@localhost">
|
||||||
|
<body>Hi Y'all</body>
|
||||||
|
</message>
|
||||||
|
</forwarded>
|
||||||
|
</result>
|
||||||
|
</message>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.recv("""
|
||||||
|
<iq type="result" id="3" to="tester@localhost">
|
||||||
|
<fin xmlns="urn:xmpp:mam:2">
|
||||||
|
<set xmlns='http://jabber.org/protocol/rsm'>
|
||||||
|
<first index='1'>28482-98726-73624</first>
|
||||||
|
<last>28482-98726-73624</last>
|
||||||
|
<count>2</count>
|
||||||
|
</set>
|
||||||
|
</fin>
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.run_coro(fut)
|
||||||
|
self.assertEqual(
|
||||||
|
msgs[0]['mam_result']['forwarded']['message']['body'],
|
||||||
|
"Hail to thee",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
msgs[1]['mam_result']['forwarded']['message']['body'],
|
||||||
|
"Hi Y'all",
|
||||||
|
)
|
||||||
|
self.assertEqual(len(msgs), 2)
|
||||||
|
|
||||||
|
def testIterate(self):
|
||||||
|
"""Test iterating over MAM messages with RSM"""
|
||||||
|
|
||||||
|
msgs = []
|
||||||
|
|
||||||
|
async def test():
|
||||||
|
iterator = self.xmpp['xep_0313'].iterate(
|
||||||
|
with_jid=JID('toto@titi'),
|
||||||
|
start='2010-06-07T00:00:00Z',
|
||||||
|
)
|
||||||
|
async for message in iterator:
|
||||||
|
msgs.append(message)
|
||||||
|
|
||||||
|
fut = self.xmpp.wrap(test())
|
||||||
|
self.wait_()
|
||||||
|
self.send("""
|
||||||
|
<iq type='set' id='2'>
|
||||||
|
<query xmlns='urn:xmpp:mam:2' queryid='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>toto@titi</value>
|
||||||
|
</field>
|
||||||
|
<field var='start'>
|
||||||
|
<value>2010-06-07T00:00:00Z</value>
|
||||||
|
</field>
|
||||||
|
</x>
|
||||||
|
<set xmlns="http://jabber.org/protocol/rsm">
|
||||||
|
<max>10</max>
|
||||||
|
</set>
|
||||||
|
</query>
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.recv("""
|
||||||
|
<message id='abc' to='tester@localhost/resource'>
|
||||||
|
<result xmlns='urn:xmpp:mam:2' queryid='2'
|
||||||
|
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="tester@localhost">
|
||||||
|
<body>Hail to thee</body>
|
||||||
|
</message>
|
||||||
|
</forwarded>
|
||||||
|
</result>
|
||||||
|
</message>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.recv("""
|
||||||
|
<iq type="result" id="2" to="tester@localhost">
|
||||||
|
<fin xmlns="urn:xmpp:mam:2">
|
||||||
|
<set xmlns='http://jabber.org/protocol/rsm'>
|
||||||
|
<first index='0'>28482-98726-73623</first>
|
||||||
|
<last>28482-98726-73623</last>
|
||||||
|
<count>2</count>
|
||||||
|
</set>
|
||||||
|
</fin>
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.send("""
|
||||||
|
<iq type='set' id='3'>
|
||||||
|
<query xmlns='urn:xmpp:mam:2' queryid='3'>
|
||||||
|
<x xmlns='jabber:x:data' type='submit'>
|
||||||
|
<field var='FORM_TYPE' type='hidden'>
|
||||||
|
<value>urn:xmpp:mam:2</value>
|
||||||
|
</field>
|
||||||
|
<field var='with'>
|
||||||
|
<value>toto@titi</value>
|
||||||
|
</field>
|
||||||
|
<field var='start'>
|
||||||
|
<value>2010-06-07T00:00:00Z</value>
|
||||||
|
</field>
|
||||||
|
</x>
|
||||||
|
<set xmlns="http://jabber.org/protocol/rsm">
|
||||||
|
<max>10</max>
|
||||||
|
<after>28482-98726-73623</after>
|
||||||
|
</set>
|
||||||
|
</query>
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.recv("""
|
||||||
|
<message id='abc' to='tester@localhost/resource'>
|
||||||
|
<result xmlns='urn:xmpp:mam:2' queryid='3'
|
||||||
|
id='28482-98726-73624'>
|
||||||
|
<forwarded xmlns='urn:xmpp:forward:0'>
|
||||||
|
<delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:26Z'/>
|
||||||
|
<message xmlns='jabber:client' from="witch@shakespeare.lit"
|
||||||
|
to="tester@localhost">
|
||||||
|
<body>Hi Y'all</body>
|
||||||
|
</message>
|
||||||
|
</forwarded>
|
||||||
|
</result>
|
||||||
|
</message>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.recv("""
|
||||||
|
<iq type="result" id="3" to="tester@localhost">
|
||||||
|
<fin xmlns="urn:xmpp:mam:2">
|
||||||
|
<set xmlns='http://jabber.org/protocol/rsm'>
|
||||||
|
<first index='1'>28482-98726-73624</first>
|
||||||
|
<last>28482-98726-73624</last>
|
||||||
|
<count>2</count>
|
||||||
|
</set>
|
||||||
|
</fin>
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.run_coro(fut)
|
||||||
|
self.assertEqual(
|
||||||
|
msgs[0]['mam_result']['forwarded']['message']['body'],
|
||||||
|
"Hail to thee",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
msgs[1]['mam_result']['forwarded']['message']['body'],
|
||||||
|
"Hi Y'all",
|
||||||
|
)
|
||||||
|
self.assertEqual(len(msgs), 2)
|
||||||
|
|
||||||
|
def test_get_metadata(self):
|
||||||
|
"""Test a MAM metadata retrieval"""
|
||||||
|
fut = self.xmpp.wrap(
|
||||||
|
self.xmpp.plugin['xep_0313'].get_archive_metadata()
|
||||||
|
)
|
||||||
|
self.wait_()
|
||||||
|
self.send("""
|
||||||
|
<iq type='get' id='1'>
|
||||||
|
<metadata xmlns='urn:xmpp:mam:2'/>
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
self.recv("""
|
||||||
|
<iq type='result' id='1'>
|
||||||
|
<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>
|
||||||
|
""")
|
||||||
|
self.run_coro(fut)
|
||||||
|
result = fut.result()
|
||||||
|
self.assertEqual(result['mam_metadata']['start']['id'], "YWxwaGEg")
|
||||||
|
self.assertEqual(
|
||||||
|
result['mam_metadata']['start']['timestamp'],
|
||||||
|
datetime.fromisoformat('2008-08-22T21:09:04+00:00')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestMAM)
|
Loading…
Reference in New Issue
Block a user