comparison sat/plugins/plugin_xep_0313.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents 1f74cd0f22c3
children 9d0df638c8b4
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SAT plugin for Message Archive Management (XEP-0313) 4 # SAT plugin for Message Archive Management (XEP-0313)
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 # Copyright (C) 2013-2016 Adrien Cossa (souliane@mailoo.org) 6 # Copyright (C) 2013-2016 Adrien Cossa (souliane@mailoo.org)
23 from sat.core.log import getLogger 23 from sat.core.log import getLogger
24 from sat.core import exceptions 24 from sat.core import exceptions
25 from sat.tools.common import data_format 25 from sat.tools.common import data_format
26 from twisted.words.protocols.jabber import jid 26 from twisted.words.protocols.jabber import jid
27 from twisted.internet import defer 27 from twisted.internet import defer
28 from zope.interface import implements 28 from zope.interface import implementer
29 from datetime import datetime 29 from datetime import datetime
30 from dateutil import tz 30 from dateutil import tz
31 from wokkel import disco 31 from wokkel import disco
32 from wokkel import data_form 32 from wokkel import data_form
33 import uuid 33 import uuid
38 38
39 39
40 log = getLogger(__name__) 40 log = getLogger(__name__)
41 41
42 PLUGIN_INFO = { 42 PLUGIN_INFO = {
43 C.PI_NAME: u"Message Archive Management", 43 C.PI_NAME: "Message Archive Management",
44 C.PI_IMPORT_NAME: u"XEP-0313", 44 C.PI_IMPORT_NAME: "XEP-0313",
45 C.PI_TYPE: u"XEP", 45 C.PI_TYPE: "XEP",
46 C.PI_PROTOCOLS: [u"XEP-0313"], 46 C.PI_PROTOCOLS: ["XEP-0313"],
47 C.PI_DEPENDENCIES: [u"XEP-0059", u"XEP-0359"], 47 C.PI_DEPENDENCIES: ["XEP-0059", "XEP-0359"],
48 C.PI_MAIN: u"XEP_0313", 48 C.PI_MAIN: "XEP_0313",
49 C.PI_HANDLER: u"yes", 49 C.PI_HANDLER: "yes",
50 C.PI_DESCRIPTION: _(u"""Implementation of Message Archive Management"""), 50 C.PI_DESCRIPTION: _("""Implementation of Message Archive Management"""),
51 } 51 }
52 52
53 MAM_PREFIX = u"mam_" 53 MAM_PREFIX = "mam_"
54 FILTER_PREFIX = MAM_PREFIX + "filter_" 54 FILTER_PREFIX = MAM_PREFIX + "filter_"
55 KEY_LAST_STANZA_ID = u"last_stanza_id" 55 KEY_LAST_STANZA_ID = "last_stanza_id"
56 MESSAGE_RESULT = "/message/result[@xmlns='{mam_ns}' and @queryid='{query_id}']" 56 MESSAGE_RESULT = "/message/result[@xmlns='{mam_ns}' and @queryid='{query_id}']"
57 MESSAGE_STANZA_ID = '/message/stanza-id[@xmlns="{ns_stanza_id}"]' 57 MESSAGE_STANZA_ID = '/message/stanza-id[@xmlns="{ns_stanza_id}"]'
58 58
59 59
60 class XEP_0313(object): 60 class XEP_0313(object):
61 def __init__(self, host): 61 def __init__(self, host):
62 log.info(_("Message Archive Management plugin initialization")) 62 log.info(_("Message Archive Management plugin initialization"))
63 self.host = host 63 self.host = host
64 self.host.registerNamespace(u"mam", mam.NS_MAM) 64 self.host.registerNamespace("mam", mam.NS_MAM)
65 self._rsm = host.plugins[u"XEP-0059"] 65 self._rsm = host.plugins["XEP-0059"]
66 self._sid = host.plugins[u"XEP-0359"] 66 self._sid = host.plugins["XEP-0359"]
67 # Deferred used to store last stanza id in order of reception 67 # Deferred used to store last stanza id in order of reception
68 self._last_stanza_id_d = defer.Deferred() 68 self._last_stanza_id_d = defer.Deferred()
69 self._last_stanza_id_d.callback(None) 69 self._last_stanza_id_d.callback(None)
70 host.bridge.addMethod( 70 host.bridge.addMethod(
71 "MAMGet", ".plugin", in_sign='sss', 71 "MAMGet", ".plugin", in_sign='sss',
72 out_sign='(a(sdssa{ss}a{ss}sa{ss})a{ss}s)', method=self._getArchives, 72 out_sign='(a(sdssa{ss}a{ss}sa{ss})a{ss}s)', method=self._getArchives,
73 async=True) 73 async_=True)
74 74
75 @defer.inlineCallbacks 75 @defer.inlineCallbacks
76 def resume(self, client): 76 def resume(self, client):
77 """Retrieve one2one messages received since the last we have in local storage""" 77 """Retrieve one2one messages received since the last we have in local storage"""
78 stanza_id_data = yield self.host.memory.storage.getPrivates( 78 stanza_id_data = yield self.host.memory.storage.getPrivates(
79 mam.NS_MAM, [KEY_LAST_STANZA_ID], profile=client.profile) 79 mam.NS_MAM, [KEY_LAST_STANZA_ID], profile=client.profile)
80 stanza_id = stanza_id_data.get(KEY_LAST_STANZA_ID) 80 stanza_id = stanza_id_data.get(KEY_LAST_STANZA_ID)
81 if stanza_id is None: 81 if stanza_id is None:
82 log.info(u"can't retrieve last stanza ID, checking history") 82 log.info("can't retrieve last stanza ID, checking history")
83 last_mess = yield self.host.memory.historyGet( 83 last_mess = yield self.host.memory.historyGet(
84 None, None, limit=1, filters={u'not_types': C.MESS_TYPE_GROUPCHAT, 84 None, None, limit=1, filters={'not_types': C.MESS_TYPE_GROUPCHAT,
85 u'last_stanza_id': True}, 85 'last_stanza_id': True},
86 profile=client.profile) 86 profile=client.profile)
87 if not last_mess: 87 if not last_mess:
88 log.info(_(u"It seems that we have no MAM history yet")) 88 log.info(_("It seems that we have no MAM history yet"))
89 return 89 return
90 stanza_id = last_mess[0][-1][u'stanza_id'] 90 stanza_id = last_mess[0][-1]['stanza_id']
91 rsm_req = rsm.RSMRequest(max_=100, after=stanza_id) 91 rsm_req = rsm.RSMRequest(max_=100, after=stanza_id)
92 mam_req = mam.MAMRequest(rsm_=rsm_req) 92 mam_req = mam.MAMRequest(rsm_=rsm_req)
93 complete = False 93 complete = False
94 count = 0 94 count = 0
95 while not complete: 95 while not complete:
96 mam_data = yield self.getArchives(client, mam_req, 96 mam_data = yield self.getArchives(client, mam_req,
97 service=client.jid.userhostJID()) 97 service=client.jid.userhostJID())
98 elt_list, rsm_response, mam_response = mam_data 98 elt_list, rsm_response, mam_response = mam_data
99 complete = mam_response[u"complete"] 99 complete = mam_response["complete"]
100 # we update MAM request for next iteration 100 # we update MAM request for next iteration
101 mam_req.rsm.after = rsm_response.last 101 mam_req.rsm.after = rsm_response.last
102 if not elt_list: 102 if not elt_list:
103 break 103 break
104 else: 104 else:
112 continue 112 continue
113 113
114 try: 114 try:
115 destinee = jid.JID(fwd_message_elt['to']) 115 destinee = jid.JID(fwd_message_elt['to'])
116 except KeyError: 116 except KeyError:
117 log.warning(_(u'missing "to" attribute in forwarded message')) 117 log.warning(_('missing "to" attribute in forwarded message'))
118 destinee = client.jid 118 destinee = client.jid
119 if destinee.userhostJID() == client.jid.userhostJID(): 119 if destinee.userhostJID() == client.jid.userhostJID():
120 # message to use, we insert the forwarded message in the normal 120 # message to use, we insert the forwarded message in the normal
121 # workflow 121 # workflow
122 client.xmlstream.dispatch(fwd_message_elt) 122 client.xmlstream.dispatch(fwd_message_elt)
123 else: 123 else:
124 # this message should be from us, we just add it to history 124 # this message should be from us, we just add it to history
125 try: 125 try:
126 from_jid = jid.JID(fwd_message_elt['from']) 126 from_jid = jid.JID(fwd_message_elt['from'])
127 except KeyError: 127 except KeyError:
128 log.warning(_(u'missing "from" attribute in forwarded message')) 128 log.warning(_('missing "from" attribute in forwarded message'))
129 from_jid = client.jid 129 from_jid = client.jid
130 if from_jid.userhostJID() != client.jid.userhostJID(): 130 if from_jid.userhostJID() != client.jid.userhostJID():
131 log.warning(_( 131 log.warning(_(
132 u'was expecting a message sent by our jid, but this one if ' 132 'was expecting a message sent by our jid, but this one if '
133 u'from {from_jid}, ignoring\n{xml}').format( 133 'from {from_jid}, ignoring\n{xml}').format(
134 from_jid=from_jid.full(), xml=mess_elt.toXml())) 134 from_jid=from_jid.full(), xml=mess_elt.toXml()))
135 continue 135 continue
136 # adding message to history 136 # adding message to history
137 mess_data = client.messageProt.parseMessage(fwd_message_elt) 137 mess_data = client.messageProt.parseMessage(fwd_message_elt)
138 try: 138 try:
139 yield client.messageProt.addToHistory(mess_data) 139 yield client.messageProt.addToHistory(mess_data)
140 except exceptions.CancelError as e: 140 except exceptions.CancelError as e:
141 log.warning( 141 log.warning(
142 u"message has not been added to history: {e}".format(e=e)) 142 "message has not been added to history: {e}".format(e=e))
143 except Exception as e: 143 except Exception as e:
144 log.error( 144 log.error(
145 u"can't add message to history: {e}\n{xml}" 145 "can't add message to history: {e}\n{xml}"
146 .format(e=e, xml=mess_elt.toXml())) 146 .format(e=e, xml=mess_elt.toXml()))
147 147
148 if not count: 148 if not count:
149 log.info(_(u"We have received no message while offline")) 149 log.info(_("We have received no message while offline"))
150 else: 150 else:
151 log.info(_(u"We have received {num_mess} message(s) while offline.") 151 log.info(_("We have received {num_mess} message(s) while offline.")
152 .format(num_mess=count)) 152 .format(num_mess=count))
153 153
154 def profileConnected(self, client): 154 def profileConnected(self, client):
155 return self.resume(client) 155 return self.resume(client)
156 156
166 @return (data_form, None): request with parsed arguments 166 @return (data_form, None): request with parsed arguments
167 or None if no MAM arguments have been found 167 or None if no MAM arguments have been found
168 """ 168 """
169 mam_args = {} 169 mam_args = {}
170 form_args = {} 170 form_args = {}
171 for arg in (u"start", u"end"): 171 for arg in ("start", "end"):
172 try: 172 try:
173 value = extra.pop(MAM_PREFIX + arg) 173 value = extra.pop(MAM_PREFIX + arg)
174 form_args[arg] = datetime.fromtimestamp(float(value), tz.tzutc()) 174 form_args[arg] = datetime.fromtimestamp(float(value), tz.tzutc())
175 except (TypeError, ValueError): 175 except (TypeError, ValueError):
176 log.warning(u"Bad value for {arg} filter ({value}), ignoring".format( 176 log.warning("Bad value for {arg} filter ({value}), ignoring".format(
177 arg=arg, value=value)) 177 arg=arg, value=value))
178 except KeyError: 178 except KeyError:
179 continue 179 continue
180 180
181 try: 181 try:
182 form_args[u"with_jid"] = jid.JID(extra.pop( 182 form_args["with_jid"] = jid.JID(extra.pop(
183 MAM_PREFIX + u"with")) 183 MAM_PREFIX + "with"))
184 except (jid.InvalidFormat): 184 except (jid.InvalidFormat):
185 log.warning(u"Bad value for jid filter") 185 log.warning("Bad value for jid filter")
186 except KeyError: 186 except KeyError:
187 pass 187 pass
188 188
189 for name, value in extra.iteritems(): 189 for name, value in extra.items():
190 if name.startswith(FILTER_PREFIX): 190 if name.startswith(FILTER_PREFIX):
191 var = name[len(FILTER_PREFIX):] 191 var = name[len(FILTER_PREFIX):]
192 extra_fields = form_args.setdefault(u"extra_fields", []) 192 extra_fields = form_args.setdefault("extra_fields", [])
193 extra_fields.append(data_form.Field(var=var, value=value)) 193 extra_fields.append(data_form.Field(var=var, value=value))
194 194
195 for arg in (u"node", u"query_id"): 195 for arg in ("node", "query_id"):
196 try: 196 try:
197 value = extra.pop(MAM_PREFIX + arg) 197 value = extra.pop(MAM_PREFIX + arg)
198 mam_args[arg] = value 198 mam_args[arg] = value
199 except KeyError: 199 except KeyError:
200 continue 200 continue
207 if form_args: 207 if form_args:
208 mam_args["form"] = mam.buildForm(**form_args) 208 mam_args["form"] = mam.buildForm(**form_args)
209 209
210 # we only set orderBy if we have other MAM args 210 # we only set orderBy if we have other MAM args
211 # else we would make a MAM query while it's not expected 211 # else we would make a MAM query while it's not expected
212 if u"order_by" in extra and mam_args: 212 if "order_by" in extra and mam_args:
213 order_by = extra.pop(u"order_by") 213 order_by = extra.pop("order_by")
214 assert isinstance(order_by, list) 214 assert isinstance(order_by, list)
215 mam_args["orderBy"] = order_by 215 mam_args["orderBy"] = order_by
216 216
217 return mam.MAMRequest(**mam_args) if mam_args else None 217 return mam.MAMRequest(**mam_args) if mam_args else None
218 218
228 If None, a new dict is created 228 If None, a new dict is created
229 @return (dict): data dict 229 @return (dict): data dict
230 """ 230 """
231 if data is None: 231 if data is None:
232 data = {} 232 data = {}
233 data[u"mam_complete"] = C.boolConst(mam_response[u'complete']) 233 data["mam_complete"] = C.boolConst(mam_response['complete'])
234 data[u"mam_stable"] = C.boolConst(mam_response[u'stable']) 234 data["mam_stable"] = C.boolConst(mam_response['stable'])
235 return data 235 return data
236 236
237 def getMessageFromResult(self, client, mess_elt, mam_req, service=None): 237 def getMessageFromResult(self, client, mess_elt, mam_req, service=None):
238 """Extract usable <message/> from MAM query result 238 """Extract usable <message/> from MAM query result
239 239
243 @param mam_req(mam.MAMRequest): request used (needed to get query_id) 243 @param mam_req(mam.MAMRequest): request used (needed to get query_id)
244 @param service(jid.JID, None): MAM service where the request has been sent 244 @param service(jid.JID, None): MAM service where the request has been sent
245 None if it's user server 245 None if it's user server
246 @return domish.Element): <message/> that can be used directly with onMessage 246 @return domish.Element): <message/> that can be used directly with onMessage
247 """ 247 """
248 if mess_elt.name != u"message": 248 if mess_elt.name != "message":
249 log.warning(u"unexpected stanza in archive: {xml}".format( 249 log.warning("unexpected stanza in archive: {xml}".format(
250 xml=mess_elt.toXml())) 250 xml=mess_elt.toXml()))
251 raise exceptions.DataError(u"Invalid element") 251 raise exceptions.DataError("Invalid element")
252 service_jid = client.jid.userhostJID() if service is None else service 252 service_jid = client.jid.userhostJID() if service is None else service
253 mess_from = mess_elt[u"from"] 253 mess_from = mess_elt["from"]
254 # we check that the message has been sent by the right service 254 # we check that the message has been sent by the right service
255 # if service is None (i.e. message expected from our own server) 255 # if service is None (i.e. message expected from our own server)
256 # from can be server jid or user's bare jid 256 # from can be server jid or user's bare jid
257 if (mess_from != service_jid.full() 257 if (mess_from != service_jid.full()
258 and not (service is None and mess_from == client.jid.host)): 258 and not (service is None and mess_from == client.jid.host)):
259 log.error(u"Message is not from our server, something went wrong: " 259 log.error("Message is not from our server, something went wrong: "
260 u"{xml}".format(xml=mess_elt.toXml())) 260 "{xml}".format(xml=mess_elt.toXml()))
261 raise exceptions.DataError(u"Invalid element") 261 raise exceptions.DataError("Invalid element")
262 try: 262 try:
263 result_elt = next(mess_elt.elements(mam.NS_MAM, u"result")) 263 result_elt = next(mess_elt.elements(mam.NS_MAM, "result"))
264 forwarded_elt = next(result_elt.elements(C.NS_FORWARD, u"forwarded")) 264 forwarded_elt = next(result_elt.elements(C.NS_FORWARD, "forwarded"))
265 try: 265 try:
266 delay_elt = next(forwarded_elt.elements(C.NS_DELAY, u"delay")) 266 delay_elt = next(forwarded_elt.elements(C.NS_DELAY, "delay"))
267 except StopIteration: 267 except StopIteration:
268 # delay_elt is not mandatory 268 # delay_elt is not mandatory
269 delay_elt = None 269 delay_elt = None
270 fwd_message_elt = next(forwarded_elt.elements(C.NS_CLIENT, u"message")) 270 fwd_message_elt = next(forwarded_elt.elements(C.NS_CLIENT, "message"))
271 except StopIteration: 271 except StopIteration:
272 log.warning(u"Invalid message received from MAM: {xml}".format( 272 log.warning("Invalid message received from MAM: {xml}".format(
273 xml=mess_elt.toXml())) 273 xml=mess_elt.toXml()))
274 raise exceptions.DataError(u"Invalid element") 274 raise exceptions.DataError("Invalid element")
275 else: 275 else:
276 if not result_elt[u"queryid"] == mam_req.query_id: 276 if not result_elt["queryid"] == mam_req.query_id:
277 log.error(u"Unexpected query id (was expecting {query_id}): {xml}" 277 log.error("Unexpected query id (was expecting {query_id}): {xml}"
278 .format(query_id=mam.query_id, xml=mess_elt.toXml())) 278 .format(query_id=mam.query_id, xml=mess_elt.toXml()))
279 raise exceptions.DataError(u"Invalid element") 279 raise exceptions.DataError("Invalid element")
280 stanza_id = self._sid.getStanzaId(fwd_message_elt, 280 stanza_id = self._sid.getStanzaId(fwd_message_elt,
281 service_jid) 281 service_jid)
282 if stanza_id is None: 282 if stanza_id is None:
283 # not stanza-id element is present, we add one so message 283 # not stanza-id element is present, we add one so message
284 # will be archived with it, and we won't request several times 284 # will be archived with it, and we won't request several times
285 # the same MAM achive 285 # the same MAM achive
286 try: 286 try:
287 stanza_id = result_elt[u"id"] 287 stanza_id = result_elt["id"]
288 except AttributeError: 288 except AttributeError:
289 log.warning(u'Invalid MAM result: missing "id" attribute: {xml}' 289 log.warning('Invalid MAM result: missing "id" attribute: {xml}'
290 .format(xml=result_elt.toXml())) 290 .format(xml=result_elt.toXml()))
291 raise exceptions.DataError(u"Invalid element") 291 raise exceptions.DataError("Invalid element")
292 self._sid.addStanzaId(client, fwd_message_elt, stanza_id, by=service_jid) 292 self._sid.addStanzaId(client, fwd_message_elt, stanza_id, by=service_jid)
293 293
294 if delay_elt is not None: 294 if delay_elt is not None:
295 fwd_message_elt.addChild(delay_elt) 295 fwd_message_elt.addChild(delay_elt)
296 296
321 elt_list.append(message_elt) 321 elt_list.append(message_elt)
322 322
323 def _queryFinished(self, iq_result, client, elt_list, event): 323 def _queryFinished(self, iq_result, client, elt_list, event):
324 client.xmlstream.removeObserver(event, self._appendMessage) 324 client.xmlstream.removeObserver(event, self._appendMessage)
325 try: 325 try:
326 fin_elt = iq_result.elements(mam.NS_MAM, "fin").next() 326 fin_elt = next(iq_result.elements(mam.NS_MAM, "fin"))
327 except StopIteration: 327 except StopIteration:
328 raise exceptions.DataError(u"Invalid MAM result") 328 raise exceptions.DataError("Invalid MAM result")
329 329
330 mam_response = {u"complete": C.bool(fin_elt.getAttribute(u"complete", C.BOOL_FALSE)), 330 mam_response = {"complete": C.bool(fin_elt.getAttribute("complete", C.BOOL_FALSE)),
331 u"stable": C.bool(fin_elt.getAttribute(u"stable", C.BOOL_TRUE))} 331 "stable": C.bool(fin_elt.getAttribute("stable", C.BOOL_TRUE))}
332 332
333 try: 333 try:
334 rsm_response = rsm.RSMResponse.fromElement(fin_elt) 334 rsm_response = rsm.RSMResponse.fromElement(fin_elt)
335 except rsm.RSMNotFoundError: 335 except rsm.RSMNotFoundError:
336 rsm_response = None 336 rsm_response = None
381 - MAM response, which is a dict with following value: 381 - MAM response, which is a dict with following value:
382 - complete: a boolean which is True if all items have been received 382 - complete: a boolean which is True if all items have been received
383 - stable: a boolean which is False if items order may be changed 383 - stable: a boolean which is False if items order may be changed
384 """ 384 """
385 if query.query_id is None: 385 if query.query_id is None:
386 query.query_id = unicode(uuid.uuid4()) 386 query.query_id = str(uuid.uuid4())
387 elt_list = [] 387 elt_list = []
388 event = MESSAGE_RESULT.format(mam_ns=mam.NS_MAM, query_id=query.query_id) 388 event = MESSAGE_RESULT.format(mam_ns=mam.NS_MAM, query_id=query.query_id)
389 client.xmlstream.addObserver(event, self._appendMessage, 0, elt_list, message_cb) 389 client.xmlstream.addObserver(event, self._appendMessage, 0, elt_list, message_cb)
390 d = self.queryArchive(client, query, service) 390 d = self.queryArchive(client, query, service)
391 d.addCallback(self._queryFinished, client, elt_list, event) 391 d.addCallback(self._queryFinished, client, elt_list, event)
429 @param message_elt(domish.Element): <message> with a stanza-id 429 @param message_elt(domish.Element): <message> with a stanza-id
430 """ 430 """
431 service_jid = client.jid.userhostJID() 431 service_jid = client.jid.userhostJID()
432 stanza_id = self._sid.getStanzaId(message_elt, service_jid) 432 stanza_id = self._sid.getStanzaId(message_elt, service_jid)
433 if stanza_id is None: 433 if stanza_id is None:
434 log.debug(u"Ignoring <message>, stanza id is not from our server") 434 log.debug("Ignoring <message>, stanza id is not from our server")
435 else: 435 else:
436 # we use self._last_stanza_id_d do be sure that last_stanza_id is stored in 436 # we use self._last_stanza_id_d do be sure that last_stanza_id is stored in
437 # the order of reception 437 # the order of reception
438 self._last_stanza_id_d.addCallback( 438 self._last_stanza_id_d.addCallback(
439 lambda __: self.host.memory.storage.setPrivateValue( 439 lambda __: self.host.memory.storage.setPrivateValue(
441 key=KEY_LAST_STANZA_ID, 441 key=KEY_LAST_STANZA_ID,
442 value=stanza_id, 442 value=stanza_id,
443 profile=client.profile)) 443 profile=client.profile))
444 444
445 445
446 @implementer(disco.IDisco)
446 class SatMAMClient(mam.MAMClient): 447 class SatMAMClient(mam.MAMClient):
447 implements(disco.IDisco)
448 448
449 def __init__(self, plugin_parent): 449 def __init__(self, plugin_parent):
450 self.plugin_parent = plugin_parent 450 self.plugin_parent = plugin_parent
451 451
452 @property 452 @property
453 def host(self): 453 def host(self):
454 return self.parent.host_app 454 return self.parent.host_app
455 455
456 def connectionInitialized(self): 456 def connectionInitialized(self):
457 observer_xpath = MESSAGE_STANZA_ID.format( 457 observer_xpath = MESSAGE_STANZA_ID.format(
458 ns_stanza_id=self.host.ns_map[u'stanza_id']) 458 ns_stanza_id=self.host.ns_map['stanza_id'])
459 self.xmlstream.addObserver( 459 self.xmlstream.addObserver(
460 observer_xpath, self.plugin_parent.onMessageStanzaId, client=self.parent 460 observer_xpath, self.plugin_parent.onMessageStanzaId, client=self.parent
461 ) 461 )
462 462
463 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): 463 def getDiscoInfo(self, requestor, target, nodeIdentifier=""):