comparison libervia/backend/plugins/plugin_xep_0313.py @ 4154:85f5e6225aa1

plugin XEP-0313: better error logging + store last stanza ID when retrieving archives + small improvments
author Goffi <goffi@goffi.org>
date Wed, 22 Nov 2023 14:56:14 +0100
parents 4b842c1fb686
children 1c30d574df2b
comparison
equal deleted inserted replaced
4153:9162d3480b9e 4154:85f5e6225aa1
16 # GNU Affero General Public License for more details. 16 # GNU Affero General Public License for more details.
17 17
18 # You should have received a copy of the GNU Affero General Public License 18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>. 19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 20
21 from datetime import datetime
22 import uuid
23
24 from dateutil import tz
25 from twisted.internet import defer
26 from twisted.words.protocols.jabber import jid
27 from twisted.words.protocols.jabber.error import StanzaError
28 from wokkel import disco
29 from wokkel import data_form
30 from wokkel import rsm
31 from wokkel import mam
32 from zope.interface import implementer
33
34 from libervia.backend.core import exceptions
21 from libervia.backend.core.constants import Const as C 35 from libervia.backend.core.constants import Const as C
22 from libervia.backend.core.i18n import _ 36 from libervia.backend.core.i18n import _
23 from libervia.backend.core.log import getLogger 37 from libervia.backend.core.log import getLogger
24 from libervia.backend.core import exceptions 38 from libervia.backend.tools import xml_tools
25 from libervia.backend.tools.common import data_format 39 from libervia.backend.tools.common import data_format
26 from twisted.words.protocols.jabber import jid
27 from twisted.internet import defer
28 from zope.interface import implementer
29 from datetime import datetime
30 from dateutil import tz
31 from wokkel import disco
32 from wokkel import data_form
33 import uuid
34
35 # XXX: mam and rsm come from sat_tmp.wokkel
36 from wokkel import rsm
37 from wokkel import mam
38 40
39 41
40 log = getLogger(__name__) 42 log = getLogger(__name__)
41 43
42 PLUGIN_INFO = { 44 PLUGIN_INFO = {
97 rsm_req = rsm.RSMRequest(max_=100, after=stanza_id) 99 rsm_req = rsm.RSMRequest(max_=100, after=stanza_id)
98 mam_req = mam.MAMRequest(rsm_=rsm_req) 100 mam_req = mam.MAMRequest(rsm_=rsm_req)
99 complete = False 101 complete = False
100 count = 0 102 count = 0
101 while not complete: 103 while not complete:
102 mam_data = await self.get_archives(client, mam_req, 104 try:
103 service=client.jid.userhostJID()) 105 mam_data = await self.get_archives(client, mam_req,
106 service=client.jid.userhostJID())
107 except StanzaError as e:
108 log.warning(
109 f"Can't retrieve MAM archives: {e}\n"
110 f"{xml_tools.pp_elt(mam_req.toElement())}\n"
111 f"{xml_tools.pp_elt(e.stanza)}"
112 )
113 return
114 except Exception as e:
115 log.exception(
116 f"Can't retrieve retrieve MAM archive"
117 )
118 return
104 elt_list, rsm_response, mam_response = mam_data 119 elt_list, rsm_response, mam_response = mam_data
105 complete = mam_response["complete"] 120 complete = mam_response["complete"]
106 # we update MAM request for next iteration 121 # we update MAM request for next iteration
107 mam_req.rsm.after = rsm_response.last 122 mam_req.rsm.after = rsm_response.last
108 # before may be set if we had no previous history 123 # before may be set if we had no previous history
110 if not elt_list: 125 if not elt_list:
111 break 126 break
112 else: 127 else:
113 count += len(elt_list) 128 count += len(elt_list)
114 129
115 for mess_elt in elt_list: 130 for idx, mess_elt in enumerate(elt_list):
116 try: 131 try:
117 fwd_message_elt = self.get_message_from_result( 132 fwd_message_elt = self.get_message_from_result(
118 client, mess_elt, mam_req) 133 client, mess_elt, mam_req)
119 except exceptions.DataError: 134 except exceptions.DataError:
120 continue 135 continue
150 "message has not been added to history: {e}".format(e=e)) 165 "message has not been added to history: {e}".format(e=e))
151 except Exception as e: 166 except Exception as e:
152 log.error( 167 log.error(
153 "can't add message to history: {e}\n{xml}" 168 "can't add message to history: {e}\n{xml}"
154 .format(e=e, xml=mess_elt.toXml())) 169 .format(e=e, xml=mess_elt.toXml()))
170 if complete and idx == len(elt_list) - 1:
171 # We are at the last message from archive, we store the ID to not
172 # ask again the same messages next time.
173 try:
174 stanza_id = mess_elt.result["id"]
175 await self.host.memory.storage.set_private_value(
176 namespace=mam.NS_MAM,
177 key=KEY_LAST_STANZA_ID,
178 value=stanza_id,
179 profile=client.profile
180 )
181 except Exception:
182 log.exception("Can't store last stanza ID")
155 183
156 if not count: 184 if not count:
157 log.info(_("We have received no message while offline")) 185 log.info(_("We have received no message while offline"))
158 else: 186 else:
159 log.info(_("We have received {num_mess} message(s) while offline.") 187 log.info(_("We have received {num_mess} message(s) while offline.")
161 189
162 def profile_connected(self, client): 190 def profile_connected(self, client):
163 defer.ensureDeferred(self.resume(client)) 191 defer.ensureDeferred(self.resume(client))
164 192
165 def get_handler(self, client): 193 def get_handler(self, client):
166 mam_client = client._mam = SatMAMClient(self) 194 mam_client = client._mam = LiberviaMAMClient(self)
167 return mam_client 195 return mam_client
168 196
169 def parse_extra(self, extra, with_rsm=True): 197 def parse_extra(self, extra, with_rsm=True):
170 """Parse extra dictionnary to retrieve MAM arguments 198 """Parse extra dictionnary to retrieve MAM arguments
171 199
238 if mess_elt.name != "message": 266 if mess_elt.name != "message":
239 log.warning("unexpected stanza in archive: {xml}".format( 267 log.warning("unexpected stanza in archive: {xml}".format(
240 xml=mess_elt.toXml())) 268 xml=mess_elt.toXml()))
241 raise exceptions.DataError("Invalid element") 269 raise exceptions.DataError("Invalid element")
242 service_jid = client.jid.userhostJID() if service is None else service 270 service_jid = client.jid.userhostJID() if service is None else service
243 mess_from = mess_elt["from"] 271 mess_from = mess_elt.getAttribute("from") or client.jid.userhostJID()
244 # we check that the message has been sent by the right service 272 # we check that the message has been sent by the right service
245 # if service is None (i.e. message expected from our own server) 273 # if service is None (i.e. message expected from our own server)
246 # from can be server jid or user's bare jid 274 # from can be server jid or user's bare jid
247 if (mess_from != service_jid.full() 275 if (mess_from != service_jid.full()
248 and not (service is None and mess_from == client.jid.host)): 276 and not (service is None and mess_from == client.jid.host)):
418 446
419 the messages' stanza ids are stored when received, so the last one can be used 447 the messages' stanza ids are stored when received, so the last one can be used
420 to retrieve missing history on next connection 448 to retrieve missing history on next connection
421 @param message_elt(domish.Element): <message> with a stanza-id 449 @param message_elt(domish.Element): <message> with a stanza-id
422 """ 450 """
451 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT:
452 # groupchat message MAM is handled by XEP-0045
453 return
423 service_jid = client.jid.userhostJID() 454 service_jid = client.jid.userhostJID()
424 stanza_id = self._sid.get_stanza_id(message_elt, service_jid) 455 stanza_id = self._sid.get_stanza_id(message_elt, service_jid)
425 if stanza_id is None: 456 if stanza_id is None:
426 log.debug("Ignoring <message>, stanza id is not from our server") 457 log.debug("Ignoring <message>, stanza id is not from our server")
427 else: 458 else:
434 value=stanza_id, 465 value=stanza_id,
435 profile=client.profile)) 466 profile=client.profile))
436 467
437 468
438 @implementer(disco.IDisco) 469 @implementer(disco.IDisco)
439 class SatMAMClient(mam.MAMClient): 470 class LiberviaMAMClient(mam.MAMClient):
440 471
441 def __init__(self, plugin_parent): 472 def __init__(self, plugin_parent):
442 self.plugin_parent = plugin_parent 473 self.plugin_parent = plugin_parent
443 474
444 @property 475 @property