diff --git a/libervia/backend/core/constants.py b/libervia/backend/core/constants.py
index d13900c7..d22d50c8 100644
--- a/libervia/backend/core/constants.py
+++ b/libervia/backend/core/constants.py
@@ -126,6 +126,7 @@ class Const(object):
MESS_UPDATE_EDIT = "EDIT"
MESS_UPDATE_REACTION = "REACTION"
+ MESS_UPDATE_RETRACT = "RETRACT"
MESS_EXTRA_INFO = "info_type"
EXTRA_INFO_DECR_ERR = "DECRYPTION_ERROR"
diff --git a/libervia/backend/models/core.py b/libervia/backend/models/core.py
index 93e895fc..ec7ad77c 100644
--- a/libervia/backend/models/core.py
+++ b/libervia/backend/models/core.py
@@ -32,11 +32,6 @@ class MessageUpdateData(BaseModel):
super().__init__(**kwargs)
-class MessageEditData(MessageData):
- """Data used when a new message edition has been received"""
- pass
-
-
class MessageReactionData(MessageUpdateData):
reactions: dict[str, list[str]] = Field(
description="Reaction to reacting entities mapping"
diff --git a/libervia/backend/plugins/plugin_comp_ap_gateway/__init__.py b/libervia/backend/plugins/plugin_comp_ap_gateway/__init__.py
index dd5bfaf7..2648938c 100644
--- a/libervia/backend/plugins/plugin_comp_ap_gateway/__init__.py
+++ b/libervia/backend/plugins/plugin_comp_ap_gateway/__init__.py
@@ -167,7 +167,7 @@ class APGateway:
self.ad_hoc = APAdHocService(self)
self.ap_events = APEvents(self)
host.trigger.add("message_received", self._message_received_trigger, priority=-1000)
- host.trigger.add("XEP-0424_retractReceived", self._on_message_retract)
+ host.trigger.add("XEP-0424_retract_received", self._on_message_retract)
host.trigger.add("XEP-0372_ref_received", self._on_reference_received)
host.bridge.add_method(
@@ -2321,7 +2321,7 @@ class APGateway:
client: SatXMPPEntity,
message_elt: domish.Element,
retract_elt: domish.Element,
- fastened_elts
+ history: History
) -> bool:
if client != self.client:
return True
@@ -2341,7 +2341,7 @@ class APGateway:
actor_id = await self.get_ap_actor_id_from_account(ap_account)
inbox = await self.get_ap_inbox_from_id(actor_id, use_shared=False)
url_actor, ap_item = await self.ap_delete_item(
- from_jid.userhostJID(), None, fastened_elts.id, public=False
+ from_jid.userhostJID(), None, retract_elt["id"], public=False
)
resp = await self.sign_and_post(inbox, url_actor, ap_item)
return False
diff --git a/libervia/backend/plugins/plugin_xep_0308.py b/libervia/backend/plugins/plugin_xep_0308.py
index a51ed626..2d3de778 100644
--- a/libervia/backend/plugins/plugin_xep_0308.py
+++ b/libervia/backend/plugins/plugin_xep_0308.py
@@ -31,7 +31,7 @@ from libervia.backend.core.core_types import SatXMPPEntity
from libervia.backend.core.i18n import _
from libervia.backend.core.log import getLogger
from libervia.backend.memory.sqla import History, Message, Subject, joinedload, select
-from libervia.backend.models.core import MessageEditData, MessageEdition
+from libervia.backend.models.core import MessageData, MessageEdition
from libervia.backend.tools.common import data_format
from libervia.backend.tools.utils import aio
log = getLogger(__name__)
@@ -140,7 +140,7 @@ class XEP_0308:
edited_history.extra.setdefault(C.MESS_EXTRA_EDITIONS, []).append(previous_version)
await self.host.memory.storage.add(edited_history)
- edit_data = MessageEditData(edited_history.serialise())
+ edit_data = MessageData(edited_history.serialise())
self.host.bridge.message_update(
edited_history.uid,
C.MESS_UPDATE_EDIT,
diff --git a/libervia/backend/plugins/plugin_xep_0424.py b/libervia/backend/plugins/plugin_xep_0424.py
index a7e16ee9..26b9ad37 100644
--- a/libervia/backend/plugins/plugin_xep_0424.py
+++ b/libervia/backend/plugins/plugin_xep_0424.py
@@ -15,22 +15,23 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from typing import Dict, Any
import time
-from copy import deepcopy
+from typing import Any, Dict
-from twisted.words.protocols.jabber import xmlstream, jid
-from twisted.words.xish import domish
+from sqlalchemy.orm.attributes import flag_modified
from twisted.internet import defer
+from twisted.words.protocols.jabber import jid, xmlstream
+from twisted.words.xish import domish
from wokkel import disco
from zope.interface import implementer
-from libervia.backend.core.constants import Const as C
-from libervia.backend.core.i18n import _, D_
from libervia.backend.core import exceptions
-from libervia.backend.core.core_types import SatXMPPEntity
+from libervia.backend.core.constants import Const as C
+from libervia.backend.core.core_types import MessageData, SatXMPPEntity
+from libervia.backend.core.i18n import D_, _
from libervia.backend.core.log import getLogger
from libervia.backend.memory.sqla_mapping import History
+from libervia.backend.tools.common import data_format
log = getLogger(__name__)
@@ -40,14 +41,14 @@ PLUGIN_INFO = {
C.PI_IMPORT_NAME: "XEP-0424",
C.PI_TYPE: "XEP",
C.PI_MODES: C.PLUG_MODE_BOTH,
- C.PI_PROTOCOLS: ["XEP-0334", "XEP-0424", "XEP-0428"],
- C.PI_DEPENDENCIES: ["XEP-0422"],
+ C.PI_PROTOCOLS: ["XEP-0424"],
+ C.PI_DEPENDENCIES: ["XEP-0334", "XEP-0428"],
C.PI_MAIN: "XEP_0424",
C.PI_HANDLER: "yes",
C.PI_DESCRIPTION: _("""Implementation Message Retraction"""),
}
-NS_MESSAGE_RETRACT = "urn:xmpp:message-retract:0"
+NS_MESSAGE_RETRACT = "urn:xmpp:message-retract:1"
CATEGORY = "Privacy"
NAME = "retract_history"
@@ -65,14 +66,13 @@ PARAMS = """
)
-class XEP_0424(object):
+class XEP_0424:
def __init__(self, host):
- log.info(_("XEP-0424 (Message Retraction) plugin initialization"))
+ log.info(f"plugin {PLUGIN_INFO[C.PI_NAME]!r} initialization")
self.host = host
host.memory.update_params(PARAMS)
self._h = host.plugins["XEP-0334"]
- self._f = host.plugins["XEP-0422"]
host.register_namespace("message-retract", NS_MESSAGE_RETRACT)
host.trigger.add("message_received", self._message_received_trigger, 100)
host.bridge.add_method(
@@ -96,7 +96,7 @@ class XEP_0424(object):
def retract_by_origin_id(
self,
client: SatXMPPEntity,
- dest_jid: jid.JID,
+ peer_jid: jid.JID,
origin_id: str
) -> None:
"""Send a message retraction using origin-id
@@ -108,9 +108,9 @@ class XEP_0424(object):
"""
message_elt = domish.Element((None, "message"))
message_elt["from"] = client.jid.full()
- message_elt["to"] = dest_jid.full()
- apply_to_elt = self._f.apply_to_elt(message_elt, origin_id)
- apply_to_elt.addElement((NS_MESSAGE_RETRACT, "retract"))
+ message_elt["to"] = peer_jid.full()
+ retract_elt = message_elt.addElement((NS_MESSAGE_RETRACT, "retract"))
+ retract_elt["id"] = origin_id
self.host.plugins["XEP-0428"].add_fallback_elt(
message_elt,
"[A message retraction has been requested, but your client doesn't support "
@@ -138,8 +138,17 @@ class XEP_0424(object):
"client is probably not supporting message retraction."
)
else:
- self.retract_by_origin_id(client, history.dest_jid, origin_id)
- await self.retract_db_history(client, history)
+ if history.type == C.MESS_TYPE_GROUPCHAT:
+ is_group_chat = True
+ peer_jid = history.dest_jid
+ else:
+ is_group_chat = False
+ peer_jid = jid.JID(history.dest)
+ self.retract_by_origin_id(client, peer_jid, origin_id)
+ if not is_group_chat:
+ # retraction will happen when message will be received in the
+ # chat.
+ await self.retract_db_history(client, history)
async def retract(
self,
@@ -157,7 +166,7 @@ class XEP_0424(object):
raise ValueError("message_id can't be empty")
history = await self.host.memory.storage.get(
client, History, History.uid, message_id,
- joined_loads=[History.messages, History.subjects]
+ joined_loads=[History.messages, History.subjects, History.thread]
)
if history is None:
raise exceptions.NotFound(
@@ -171,12 +180,10 @@ class XEP_0424(object):
@param history: history instance
"messages" and "subjects" must be loaded too
"""
- # FIXME: should be keep history? This is useful to check why a message has been
+ # FIXME: should we keep history? This is useful to check why a message has been
# retracted, but if may be bad if the user think it's really deleted
- # we assign a new object to be sure to trigger an update
- history.extra = deepcopy(history.extra) if history.extra else {}
- history.extra["retracted"] = True
- keep_history = self.host.memory.param_get_a(
+ flag_modified(history, "extra")
+ keep_history = await self.host.memory.param_get_a_async(
NAME, CATEGORY, profile_key=client.profile
)
old_version: Dict[str, Any] = {
@@ -185,13 +192,27 @@ class XEP_0424(object):
if keep_history:
old_version.update({
"messages": [m.serialise() for m in history.messages],
- "subjects": [s.serialise() for s in history.subjects]
+ "subjects": [s.serialise() for s in history.subjects],
})
history.extra.setdefault("old_versions", []).append(old_version)
- await self.host.memory.storage.delete(
- history.messages + history.subjects,
- session_add=[history]
+
+ history.messages.clear()
+ history.subjects.clear()
+ history.extra["retracted"] = True
+ # we remove editions history to keep no trace of old messages
+ if "editions" in history.extra:
+ del history.extra["editions"]
+ if "attachments" in history.extra:
+ del history.extra["attachments"]
+ await self.host.memory.storage.add(history)
+
+ retract_data = MessageData(history.serialise())
+ self.host.bridge.message_update(
+ history.uid,
+ C.MESS_UPDATE_RETRACT,
+ data_format.serialise(retract_data),
+ client.profile,
)
async def _message_received_trigger(
@@ -200,39 +221,48 @@ class XEP_0424(object):
message_elt: domish.Element,
post_treat: defer.Deferred
) -> bool:
- fastened_elts = await self._f.get_fastened_elts(client, message_elt)
- if fastened_elts is None:
- return True
- for elt in fastened_elts.elements:
- if elt.name == "retract" and elt.uri == NS_MESSAGE_RETRACT:
- if fastened_elts.history is not None:
- source_jid = fastened_elts.history.source_jid
- from_jid = jid.JID(message_elt["from"])
- if source_jid.userhostJID() != from_jid.userhostJID():
- log.warning(
- f"Received message retraction from {from_jid.full()}, but "
- f"the message to retract is from {source_jid.full()}. This "
- f"maybe a hack attempt.\n{message_elt.toXml()}"
- )
- return False
- break
- else:
+ retract_elt = next(message_elt.elements(NS_MESSAGE_RETRACT, "retract"), None)
+ if not retract_elt:
return True
+ try:
+ if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT:
+ col_id = History.stanza_id
+ else:
+ col_id = History.origin_id
+ history = await self.host.memory.storage.get(
+ client, History, col_id, retract_elt["id"],
+ joined_loads=[History.messages, History.subjects, History.thread]
+ )
+ except KeyError:
+ log.warning(f"invalid retract element, missing id: {retract_elt.toXml()}")
+ return False
+ from_jid = jid.JID(message_elt["from"])
+
+ if (
+ history is not None
+ and history.source_jid.userhostJID() != from_jid.userhostJID()
+ ):
+ log.warning(
+ f"Received message retraction from {from_jid.full()}, but the message to "
+ f"retract is from {history.source_jid.full()}. This maybe a hack "
+ f"attempt.\n{message_elt.toXml()}"
+ )
+ return False
+
if not await self.host.trigger.async_point(
- "XEP-0424_retractReceived", client, message_elt, elt, fastened_elts
+ "XEP-0424_retract_received", client, message_elt, retract_elt, history
):
return False
- if fastened_elts.history is None:
+
+ if history is None:
# we check history after the trigger because we may be in a component which
# doesn't store messages in database.
log.warning(
f"No message found with given origin-id: {message_elt.toXml()}"
)
return False
- log.info(f"[{client.profile}] retracting message {fastened_elts.id!r}")
- await self.retract_db_history(client, fastened_elts.history)
- # TODO: send bridge signal
-
+ log.info(f"[{client.profile}] retracting message {history.uid!r}")
+ await self.retract_db_history(client, history)
return False
diff --git a/libervia/backend/plugins/plugin_xep_0428.py b/libervia/backend/plugins/plugin_xep_0428.py
index aa36d7cb..9753626f 100644
--- a/libervia/backend/plugins/plugin_xep_0428.py
+++ b/libervia/backend/plugins/plugin_xep_0428.py
@@ -33,6 +33,7 @@ PLUGIN_INFO = {
C.PI_NAME: "Fallback Indication",
C.PI_IMPORT_NAME: "XEP-0428",
C.PI_TYPE: "XEP",
+ C.PI_MODES: C.PLUG_MODE_BOTH,
C.PI_PROTOCOLS: ["XEP-0428"],
C.PI_MAIN: "XEP_0428",
C.PI_HANDLER: "yes",