From 27e23672c105c0ace6efb86ee9114645dd7d90dc Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 24 Sep 2017 17:43:06 +0200 Subject: [PATCH] Update the MAM plugin for asyncio & new namespace And add an example --- examples/mam.py | 96 ++++++++++++++++++++++++++++++ slixmpp/plugins/xep_0313/mam.py | 35 +++++++++-- slixmpp/plugins/xep_0313/stanza.py | 77 +++++++++++++++--------- 3 files changed, 175 insertions(+), 33 deletions(-) create mode 100755 examples/mam.py diff --git a/examples/mam.py b/examples/mam.py new file mode 100755 index 00000000..7d55645c --- /dev/null +++ b/examples/mam.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2017 Mathieu Pasquet + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +import logging +from getpass import getpass +from argparse import ArgumentParser + +import slixmpp +from slixmpp.exceptions import XMPPError +from slixmpp import asyncio + +log = logging.getLogger(__name__) + + +class MAM(slixmpp.ClientXMPP): + + """ + A basic client fetching mam archive messages + """ + + def __init__(self, jid, password, remote_jid, start): + slixmpp.ClientXMPP.__init__(self, jid, password) + self.remote_jid = remote_jid + self.start_date = start + + self.add_event_handler("session_start", self.start) + + async def start(self, *args): + """ + Fetch mam results for the specified JID. + Use RSM to paginate the results. + """ + iq = self.make_iq_get() + results = self.plugin['xep_0313'].retrieve(jid=self.remote_jid, iterator=True, rsm={'max': 10}, start=self.start_date) + page = 1 + async for rsm in results: + print('Page %s' % page) + for msg in rsm['mam']['results']: + print('%s: %s' % (msg['mam_result']['forwarded']['stanza']['from'], msg['mam_result']['forwarded']['stanza']['body'])) + page += 1 + self.disconnect() + +if __name__ == '__main__': + # Setup the command line arguments. + parser = ArgumentParser() + parser.add_argument("-q","--quiet", help="set logging to ERROR", + action="store_const", + dest="loglevel", + const=logging.ERROR, + default=logging.INFO) + parser.add_argument("-d","--debug", help="set logging to DEBUG", + action="store_const", + dest="loglevel", + const=logging.DEBUG, + default=logging.INFO) + + # JID and password options. + parser.add_argument("-j", "--jid", dest="jid", + help="JID to use") + parser.add_argument("-p", "--password", dest="password", + help="password to use") + + # Other options + parser.add_argument("-r", "--remote-jid", dest="remote_jid", + help="Remote JID") + parser.add_argument("--start", help="Start date", default='2017-09-20T12:00:00Z') + + args = parser.parse_args() + + # Setup logging. + logging.basicConfig(level=args.loglevel, + format='%(levelname)-8s %(message)s') + + if args.jid is None: + args.jid = input("Username: ") + if args.password is None: + args.password = getpass("Password: ") + if args.remote_jid is None: + args.remote_jid = input("Remote JID: ") + if args.start is None: + args.start = input("Start time: ") + + xmpp = MAM(args.jid, args.password, args.remote_jid, args.start) + xmpp.register_plugin('xep_0313') + + # Connect to the XMPP server and start processing XMPP stanzas. + xmpp.connect() + xmpp.process(forever=False) diff --git a/slixmpp/plugins/xep_0313/mam.py b/slixmpp/plugins/xep_0313/mam.py index 37aa49b4..3f543c23 100644 --- a/slixmpp/plugins/xep_0313/mam.py +++ b/slixmpp/plugins/xep_0313/mam.py @@ -36,35 +36,58 @@ class XEP_0313(BasePlugin): register_stanza_plugin(Iq, stanza.MAM) register_stanza_plugin(Iq, stanza.Preferences) register_stanza_plugin(Message, stanza.Result) - register_stanza_plugin(Message, stanza.Archived, iterable=True) + register_stanza_plugin(Iq, stanza.Fin) 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.Fin, self.xmpp['xep_0059'].stanza.Set) def retrieve(self, jid=None, start=None, end=None, with_jid=None, ifrom=None, - timeout=None, callback=None, iterator=False): + timeout=None, callback=None, iterator=False, rsm=None): iq = self.xmpp.Iq() query_id = iq['id'] iq['to'] = jid iq['from'] = ifrom - iq['type'] = 'get' + iq['type'] = 'set' iq['mam']['queryid'] = query_id iq['mam']['start'] = start iq['mam']['end'] = end iq['mam']['with'] = with_jid + if rsm: + for key, value in rsm.items(): + iq['mam']['rsm'][key] = str(value) + + cb_data = {} + def pre_cb(query): + query['mam']['queryid'] = query['id'] + collector = Collector( + 'MAM_Results_%s' % query_id, + StanzaPath('message/mam_result@queryid=%s' % query['id'])) + self.xmpp.register_handler(collector) + cb_data['collector'] = collector + + def post_cb(result): + results = cb_data['collector'].stop() + if result['type'] == 'result': + result['mam']['results'] = results + + if iterator: + return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results', + recv_interface='mam_fin', + pre_cb=pre_cb, post_cb=post_cb) 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') def wrapped_cb(iq): results = collector.stop() if iq['type'] == 'result': iq['mam']['results'] = results - callback(iq) + if callback: + callback(iq) + return iq.send(timeout=timeout, callback=wrapped_cb) def set_preferences(self, jid=None, default=None, always=None, never=None, diff --git a/slixmpp/plugins/xep_0313/stanza.py b/slixmpp/plugins/xep_0313/stanza.py index 3075c48e..4e659b7b 100644 --- a/slixmpp/plugins/xep_0313/stanza.py +++ b/slixmpp/plugins/xep_0313/stanza.py @@ -10,44 +10,76 @@ import datetime as dt from slixmpp.jid import JID from slixmpp.xmlstream import ElementBase, ET -from slixmpp.plugins import xep_0082 +from slixmpp.plugins import xep_0082, xep_0004 class MAM(ElementBase): name = 'query' - namespace = 'urn:xmpp:mam:tmp' + namespace = 'urn:xmpp:mam:2' plugin_attrib = 'mam' interfaces = {'queryid', 'start', 'end', 'with', 'results'} sub_interfaces = {'start', 'end', 'with'} 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): - timestamp = self._get_sub_text('start') - return xep_0082.parse(timestamp) + fields = self.__get_fields() + field = fields.get('start') + if field: + return xep_0082.parse(field['value']) def set_start(self, value): if isinstance(value, dt.datetime): value = xep_0082.format_datetime(value) - self._set_sub_text('start', 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 def get_end(self): - timestamp = self._get_sub_text('end') - return xep_0082.parse(timestamp) + fields = self.__get_fields() + field = fields.get('end') + if field: + return xep_0082.parse(field['value']) def set_end(self, value): if isinstance(value, dt.datetime): value = xep_0082.format_datetime(value) - self._set_sub_text('end', 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 def get_with(self): - return JID(self._get_sub_text('with')) + fields = self.__get_fields() + field = fields.get('with') + if field: + return JID(field['value']) def set_with(self, value): - self._set_sub_text('with', str(value)) - + fields = self.__get_fields() + field = fields.get('with') + if field: + field['with'] = str(value) + else: + field = self._form.add_field(var='with') + field['value'] = str(value) # The results interface is meant only as an easy # way to access the set of collected message responses # from the query. @@ -64,7 +96,7 @@ class MAM(ElementBase): class Preferences(ElementBase): name = 'prefs' - namespace = 'urn:xmpp:mam:tmp' + namespace = 'urn:xmpp:mam:2' plugin_attrib = 'mam_prefs' interfaces = {'default', 'always', 'never'} sub_interfaces = {'always', 'never'} @@ -118,22 +150,13 @@ class Preferences(ElementBase): never.append(jid_xml) +class Fin(ElementBase): + name = 'fin' + namespace = 'urn:xmpp:mam:2' + plugin_attrib = 'mam_fin' + class Result(ElementBase): name = 'result' - namespace = 'urn:xmpp:mam:tmp' + namespace = 'urn:xmpp:mam:2' plugin_attrib = 'mam_result' interfaces = {'queryid', 'id'} - - -class Archived(ElementBase): - name = 'archived' - namespace = 'urn:xmpp:mam:tmp' - plugin_attrib = 'mam_archived' - plugin_multi_attrib = 'mam_archives' - interfaces = {'by', 'id'} - - def get_by(self): - return JID(self._get_attr('by')) - - def set_by(self, value): - return self._set_attr('by', str(value))