Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dev.gajim.org/gajim/gajim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorwurstsalat <mailtrash@posteo.de>2023-05-13 20:31:34 +0300
committerwurstsalat <mailtrash@posteo.de>2023-06-07 23:21:23 +0300
commit6dfa4645b350dada15eaa07519de2584963e05ff (patch)
tree647558e3cfca8528a21784aac64ca639966ba535
parent39d250ac590beb6aaebc5274038ede1a9dc6a5d1 (diff)
feat: Add support for XEP-0424 (Message Retraction)message-retraction
-rw-r--r--gajim/common/const.py1
-rw-r--r--gajim/common/events.py8
-rw-r--r--gajim/common/modules/mam.py8
-rw-r--r--gajim/common/modules/message.py8
-rw-r--r--gajim/common/modules/message_retraction.py84
-rw-r--r--gajim/common/modules/util.py38
-rw-r--r--gajim/common/storage/archive.py87
7 files changed, 234 insertions, 0 deletions
diff --git a/gajim/common/const.py b/gajim/common/const.py
index dd1e6b3c1..29d8dc198 100644
--- a/gajim/common/const.py
+++ b/gajim/common/const.py
@@ -923,6 +923,7 @@ COMMON_FEATURES = [
Namespace.JINGLE_IBB,
Namespace.AVATAR_METADATA + '+notify',
Namespace.MESSAGE_MODERATE,
+ Namespace.MESSAGE_RETRACT,
Namespace.OMEMO_TEMP_DL + '+notify'
]
diff --git a/gajim/common/events.py b/gajim/common/events.py
index 59ea378af..1e7847865 100644
--- a/gajim/common/events.py
+++ b/gajim/common/events.py
@@ -440,6 +440,14 @@ class MessageError(ApplicationEvent):
@dataclass
+class MessageRetractionReceived(ApplicationEvent):
+ name: str = field(init=False, default='message-retraction-received')
+ account: str
+ jid: JID
+ origin_id: str
+
+
+@dataclass
class RosterItemExchangeEvent(ApplicationEvent):
name: str = field(init=False, default='roster-item-exchange')
client: 'Client'
diff --git a/gajim/common/modules/mam.py b/gajim/common/modules/mam.py
index acd8e3710..9bd05f3a4 100644
--- a/gajim/common/modules/mam.py
+++ b/gajim/common/modules/mam.py
@@ -54,6 +54,7 @@ from gajim.common.modules.base import BaseModule
from gajim.common.modules.misc import parse_oob
from gajim.common.modules.util import as_task
from gajim.common.modules.util import check_if_message_correction
+from gajim.common.modules.util import check_if_message_retraction
from gajim.common.modules.util import get_eme_message
@@ -325,6 +326,13 @@ class MAM(BaseModule):
self._log):
return
+ if check_if_message_retraction(properties,
+ self._account,
+ properties.jid,
+ kind,
+ self._log):
+ return
+
app.storage.archive.insert_into_logs(
self._account,
jid,
diff --git a/gajim/common/modules/message.py b/gajim/common/modules/message.py
index dff03c1d6..a99cdd97d 100644
--- a/gajim/common/modules/message.py
+++ b/gajim/common/modules/message.py
@@ -40,6 +40,7 @@ from gajim.common.modules.contacts import GroupchatParticipant
from gajim.common.modules.misc import parse_oob
from gajim.common.modules.misc import parse_xhtml
from gajim.common.modules.util import check_if_message_correction
+from gajim.common.modules.util import check_if_message_retraction
from gajim.common.modules.util import get_eme_message
from gajim.common.structs import OutgoingMessage
@@ -190,6 +191,13 @@ class Message(BaseModule):
self._log):
return
+ if check_if_message_retraction(properties,
+ self._account,
+ from_,
+ kind,
+ self._log):
+ return
+
if type_.is_groupchat:
if not msgtxt:
return
diff --git a/gajim/common/modules/message_retraction.py b/gajim/common/modules/message_retraction.py
new file mode 100644
index 000000000..eef727554
--- /dev/null
+++ b/gajim/common/modules/message_retraction.py
@@ -0,0 +1,84 @@
+# This file is part of Gajim.
+#
+# Gajim is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published
+# by the Free Software Foundation; version 3 only.
+#
+# Gajim is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
+
+# Message Retraction (XEP-0424)
+
+from __future__ import annotations
+
+import nbxmpp
+from nbxmpp.namespaces import Namespace
+from nbxmpp.protocol import JID
+from nbxmpp.protocol import Message
+from nbxmpp.structs import MessageProperties
+from nbxmpp.structs import StanzaHandler
+
+from gajim.common import app
+from gajim.common import types
+from gajim.common.events import MessageRetractionReceived
+from gajim.common.modules.base import BaseModule
+
+
+class MessageRetraction(BaseModule):
+
+ _nbxmpp_extends = 'MessageRetraction'
+
+ def __init__(self, client: types.Client) -> None:
+ BaseModule.__init__(self, client)
+
+ self.handlers = [
+ StanzaHandler(name='message',
+ callback=self._process_message_retraction,
+ ns=Namespace.MESSAGE_RETRACT,
+ priority=47)
+ ]
+
+ def _process_message_retraction(self,
+ _client: types.xmppClient,
+ _stanza: Message,
+ properties: MessageProperties
+ ) -> None:
+
+ if properties.message_retraction is None:
+ return
+
+ if properties.type.is_error:
+ return
+
+ # TODO: make sure to use correct jid here
+ jid = properties.jid
+ assert jid is not None
+
+ success = app.storage.archive.try_message_retraction(
+ self._account,
+ jid,
+ properties.message_retraction.origin_id,
+ properties.occupant_id)
+
+ if not success:
+ self._log.warning(
+ 'Received invalid message retraction request from %s', jid)
+ return
+
+ app.ged.raise_event(MessageRetractionReceived(
+ account=self._account,
+ jid=jid,
+ origin_id=properties.message_retraction.origin_id))
+ raise nbxmpp.NodeProcessed
+
+ def retract_message(self,
+ jid: JID,
+ message_id: str
+ ) -> None:
+
+ pass
diff --git a/gajim/common/modules/util.py b/gajim/common/modules/util.py
index a8a6582a2..a5492c961 100644
--- a/gajim/common/modules/util.py
+++ b/gajim/common/modules/util.py
@@ -36,6 +36,7 @@ from gajim.common import app
from gajim.common import types
from gajim.common.const import EME_MESSAGES
from gajim.common.const import KindConstant
+from gajim.common.events import MessageRetractionReceived
from gajim.common.events import MessageUpdated
from gajim.common.modules.misc import parse_correction
@@ -130,6 +131,43 @@ def as_task(func):
return func_wrapper
+def check_if_message_retraction(properties: MessageProperties,
+ account: str,
+ jid: JID,
+ kind: KindConstant,
+ logger: LoggerAdapter[logging.Logger]
+ ) -> bool:
+
+ if properties.message_retraction is None:
+ return False
+
+ if properties.type.is_error:
+ return False
+
+ # TODO: make sure to use correct jid here
+ #jid = properties.jid
+ assert jid is not None
+
+ successful = app.storage.archive.try_message_retraction(
+ account,
+ jid,
+ properties.message_retraction.origin_id,
+ properties.is_mam_message,
+ properties.from_muc,
+ properties.occupant_id)
+
+ if not successful:
+ logger.warning(
+ 'Received invalid message retraction request from %s', jid)
+ return False
+
+ app.ged.raise_event(MessageRetractionReceived(
+ account=account,
+ jid=jid,
+ origin_id=properties.message_retraction.origin_id))
+ return True
+
+
def check_if_message_correction(properties: MessageProperties,
account: str,
jid: JID,
diff --git a/gajim/common/storage/archive.py b/gajim/common/storage/archive.py
index 3ce90d309..3baa0bb36 100644
--- a/gajim/common/storage/archive.py
+++ b/gajim/common/storage/archive.py
@@ -1049,6 +1049,93 @@ class MessageArchiveStorage(SqliteStorage):
return True
@timeit
+ def try_message_retraction(self,
+ account: str,
+ jid: JID,
+ origin_id: str,
+ is_mam_message: bool,
+ is_groupchat: bool,
+ occupant_id: Optional[str]
+ ) -> bool:
+
+ '''Try to retract a message (XEP-0424)
+
+ :param jid: This can be a full jid or bare jid.
+ '''
+
+ account_id = self.get_account_id(account)
+
+ self._log.debug(
+ 'Check if message is retractable, parameters: %s %s %s %s',
+ jid, account_id, origin_id, occupant_id)
+
+ if is_groupchat and occupant_id is None:
+ # Group chat messages without occupant ID cannot be retracted
+ self._log.warning(
+ 'Message retraction in MUC (%s) failed: no occupant ID', jid)
+ return False
+
+ if is_groupchat:
+ sql = '''SELECT log_line_id, message, additional_data
+ FROM logs
+ NATURAL JOIN jids jid_id
+ WHERE +jid = ?
+ AND account_id = ?
+ AND message_id = ?
+ AND occupant_id = ?
+ '''
+ rows = self._con.execute(
+ sql,
+ (jid,
+ account_id,
+ origin_id,
+ occupant_id)).fetchall()
+ else:
+ sql = '''SELECT log_line_id, message, additional_data
+ FROM logs
+ NATURAL JOIN jids jid_id
+ WHERE +jid = ?
+ AND account_id = ?
+ AND message_id = ?
+ '''
+ rows = self._con.execute(
+ sql,
+ (jid,
+ account_id,
+ origin_id)).fetchall()
+
+ if not rows:
+ self._log.debug('No retractable messages found')
+ return False
+
+ if len(rows) != 1:
+ self._log.warning('More than one retractable message found')
+ return False
+
+ row = rows[0]
+
+ if row.additional_data is None:
+ additional_data = AdditionalDataDict()
+ else:
+ additional_data = row.additional_data
+
+ additional_data.set_value(
+ 'retracted', 'by', str(jid))
+ additional_data.set_value(
+ 'retracted', 'timestamp', '1')
+ serialized_dict = json.dumps(additional_data.data)
+
+ sql = '''
+ UPDATE logs SET message = ?, additional_data = ?
+ WHERE log_line_id = ?
+ '''
+ self._con.execute(
+ sql,
+ ('TODO: retracted placeholder', serialized_dict, row.log_line_id))
+
+ return True
+
+ @timeit
def update_additional_data(self,
account: str,
stanza_id: str,