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

dev.gajim.org/gajim/python-nbxmpp.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorwurstsalat <mailtrash@posteo.de>2022-12-04 19:03:03 +0300
committerPhilipp Hörist <philipp@hoerist.com>2023-02-03 18:50:35 +0300
commit8883336f27cff82dc847acf1fc2f266a69475b1c (patch)
tree47e26046df5018209961ec9f917e37e44b51a0b4
parent345fbe0dacb7ac25dd531744df5cf9b8060e13bd (diff)
feat: Add support for XEP-0461: Message Replies
-rw-r--r--nbxmpp/dispatcher.py2
-rw-r--r--nbxmpp/modules/replies.py98
-rw-r--r--nbxmpp/namespaces.py2
-rw-r--r--nbxmpp/protocol.py24
-rw-r--r--nbxmpp/structs.py8
-rw-r--r--python-nbxmpp.doap7
6 files changed, 141 insertions, 0 deletions
diff --git a/nbxmpp/dispatcher.py b/nbxmpp/dispatcher.py
index 0a232b2..b7c5450 100644
--- a/nbxmpp/dispatcher.py
+++ b/nbxmpp/dispatcher.py
@@ -72,6 +72,7 @@ from nbxmpp.modules.chat_markers import ChatMarkers
from nbxmpp.modules.receipts import Receipts
from nbxmpp.modules.oob import OOB
from nbxmpp.modules.correction import Correction
+from nbxmpp.modules.replies import Replies
from nbxmpp.modules.attention import Attention
from nbxmpp.modules.security_labels import SecurityLabels
from nbxmpp.modules.chatstates import Chatstates
@@ -185,6 +186,7 @@ class StanzaDispatcher(Observable):
self._modules['Discovery'] = Discovery(self._client)
self._modules['ChatMarkers'] = ChatMarkers(self._client)
self._modules['Receipts'] = Receipts(self._client)
+ self._modules['Replies'] = Replies(self._client)
self._modules['OOB'] = OOB(self._client)
self._modules['Correction'] = Correction(self._client)
self._modules['Attention'] = Attention(self._client)
diff --git a/nbxmpp/modules/replies.py b/nbxmpp/modules/replies.py
new file mode 100644
index 0000000..8f91b5a
--- /dev/null
+++ b/nbxmpp/modules/replies.py
@@ -0,0 +1,98 @@
+# This file is part of nbxmpp.
+#
+# This program 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; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program 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 this program; If not, see <http://www.gnu.org/licenses/>.
+
+# XEP-0461: Message Replies
+
+from __future__ import annotations
+
+from typing import Optional
+from typing import TYPE_CHECKING
+
+from nbxmpp.modules.base import BaseModule
+from nbxmpp.namespaces import Namespace
+from nbxmpp.protocol import Message
+from nbxmpp.structs import MessageProperties
+from nbxmpp.structs import ReplyData
+from nbxmpp.structs import StanzaHandler
+
+if TYPE_CHECKING:
+ from nbxmpp.client import Client
+
+
+class Replies(BaseModule):
+ def __init__(self, client: Client) -> None:
+ BaseModule.__init__(self, client)
+
+ self._client = client
+ self.handlers = [
+ StanzaHandler(name='message',
+ callback=self._process_message,
+ ns=Namespace.REPLY,
+ priority=15)
+ ]
+
+ def _process_message(self,
+ _client: Client,
+ stanza: Message,
+ properties: MessageProperties
+ ) -> None:
+
+ reply = stanza.getTag('reply', namespace=Namespace.REPLY)
+ if reply is None:
+ return
+
+ reply_to = reply.getAttr('to')
+ if reply_to is None:
+ self._log.warning('Received reply without "to" attribute')
+ return
+
+ reply_to_id = reply.getAttr('id')
+ if reply_to_id is None:
+ self._log.warning('Received reply without "id"')
+ return
+
+ fallback_start, fallback_end = None, None
+ fallback_data = self._get_fallback_data(stanza)
+ if fallback_data is not None:
+ fallback_start, fallback_end = fallback_data
+
+ properties.reply_data = ReplyData(
+ to=reply_to,
+ id=reply_to_id,
+ fallback_start=fallback_start,
+ fallback_end=fallback_end)
+
+ def _get_fallback_data(self, stanza: Message) -> Optional[tuple[int, int]]:
+ fallback = stanza.getTag('fallback', namespace=Namespace.FALLBACK)
+ if fallback is None or fallback.getAttr('for') != Namespace.REPLY:
+ return None
+
+ fallback_data = fallback.getTag('body')
+ if fallback_data is None:
+ return None
+
+ start = fallback_data.getAttr('start')
+ end = fallback_data.getAttr('end')
+ if start is None or end is None:
+ return None
+
+ try:
+ start = int(start)
+ end = int(end)
+ except ValueError:
+ self._log.warning('Could not get fallback start/end')
+ return None
+
+ return start, end
diff --git a/nbxmpp/namespaces.py b/nbxmpp/namespaces.py
index c5bd822..dc8a661 100644
--- a/nbxmpp/namespaces.py
+++ b/nbxmpp/namespaces.py
@@ -62,6 +62,7 @@ class _Namespaces:
DOMAIN_BASED_NAME: str = 'urn:xmpp:domain-based-name:1'
EME: str = 'urn:xmpp:eme:0'
ENCRYPTED: str = 'jabber:x:encrypted'
+ FALLBACK: str = 'urn:xmpp:fallback:0'
FASTEN: str = 'urn:xmpp:fasten:0'
FILE_METADATA: str = 'urn:xmpp:file:metadata:0'
FORWARD: str = 'urn:xmpp:forward:0'
@@ -138,6 +139,7 @@ class _Namespaces:
RECEIPTS: str = 'urn:xmpp:receipts'
REGISTER: str = 'jabber:iq:register'
REGISTER_FEATURE: str = 'http://jabber.org/features/iq-register'
+ REPLY: str = 'urn:xmpp:reply:0'
REPORTING: str = 'urn:xmpp:reporting:0'
ROSTER: str = 'jabber:iq:roster'
ROSTERNOTES: str = 'storage:rosternotes'
diff --git a/nbxmpp/protocol.py b/nbxmpp/protocol.py
index a6822c8..7ff4595 100644
--- a/nbxmpp/protocol.py
+++ b/nbxmpp/protocol.py
@@ -1258,6 +1258,30 @@ class Message(Protocol):
def setReceiptReceived(self, id_):
self.setTag('received', namespace=Namespace.RECEIPTS, attrs={'id': id_})
+ def setReply(self,
+ recipient_jid: str,
+ reply_to_id: str,
+ fallback_start: int,
+ fallback_end: int
+ ) -> None:
+
+ self.setTag(
+ 'reply',
+ namespace=Namespace.REPLY,
+ attrs={
+ 'id': reply_to_id,
+ 'to': recipient_jid})
+
+ fallback_tag = self.setTag(
+ 'fallback',
+ namespace=Namespace.FALLBACK,
+ attrs={'for': Namespace.REPLY})
+ fallback_tag.addChild(
+ 'body',
+ attrs={
+ 'start': str(fallback_start),
+ 'end': str(fallback_end)})
+
def setOOB(self, url, desc=None):
oob = self.setTag('x', namespace=Namespace.X_OOB)
oob.setTagData('url', url)
diff --git a/nbxmpp/structs.py b/nbxmpp/structs.py
index a54ecdd..663b49b 100644
--- a/nbxmpp/structs.py
+++ b/nbxmpp/structs.py
@@ -290,6 +290,13 @@ class CorrectionData(NamedTuple):
id: str
+class ReplyData(NamedTuple):
+ to: str
+ id: str
+ fallback_start: Optional[int]
+ fallback_end: Optional[int]
+
+
class ModerationData(NamedTuple):
stanza_id: str
moderator_jid: str
@@ -993,6 +1000,7 @@ class MessageProperties:
receipt: Optional[ReceiptData] = None
oob: Optional[OOBData] = None
correction: Optional[CorrectionData] = None
+ reply_data: Optional[ReplyData] = None
moderation: Optional[ModerationData] = None
attention: bool = False
forms = None
diff --git a/python-nbxmpp.doap b/python-nbxmpp.doap
index 65135bb..9e95da2 100644
--- a/python-nbxmpp.doap
+++ b/python-nbxmpp.doap
@@ -422,5 +422,12 @@
<xmpp:version>0.1.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0461.html"/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>0.1.0</xmpp:version>
+ </xmpp:SupportedXep>
+ </implements>
</Project>
</rdf:RDF>