Mercurial > libervia-backend
changeset 4382:b897d98b2c51 default tip
plugin XEP-0297: Reworked `forward` method and add bridge method:
`Forward` method has been reworked and now includes a fallback. XEP-0297 ask to not use
fallback, but following a discussion on xsf@, we agreed that this is a legacy thing and a
fallback should nowadays be used, I'll propose a patch to the specification.
A `message_forward` has been added to bridge.
rel 461
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 04 Jul 2025 12:33:42 +0200 |
parents | 3c97717fd662 |
children | |
files | libervia/backend/plugins/plugin_xep_0297.py |
diffstat | 1 files changed, 123 insertions(+), 38 deletions(-) [+] |
line wrap: on
line diff
--- a/libervia/backend/plugins/plugin_xep_0297.py Fri Jul 04 12:30:20 2025 +0200 +++ b/libervia/backend/plugins/plugin_xep_0297.py Fri Jul 04 12:33:42 2025 +0200 @@ -18,41 +18,65 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +from datetime import datetime +from typing import cast + +from dateutil import tz +from twisted.internet import defer +from twisted.words.protocols.jabber import jid +from twisted.words.protocols.jabber.xmlstream import XMPPHandler +from twisted.words.xish import domish +from wokkel import disco, iwokkel +from zope.interface import implementer + +from libervia.backend import G +from libervia.backend.core import exceptions from libervia.backend.core.constants import Const as C -from libervia.backend.core.i18n import _, D_ +from libervia.backend.core.core_types import SatXMPPEntity +from libervia.backend.models.core import MessageData +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.plugins.plugin_xep_0428 import XEP_0428 +from libervia.backend.tools.common.date_utils import date_fmt -from twisted.internet import defer + log = getLogger(__name__) -from wokkel import disco, iwokkel -try: - from twisted.words.protocols.xmlstream import XMPPHandler -except ImportError: - from wokkel.subprotocols import XMPPHandler -from zope.interface import implementer - -from twisted.words.xish import domish PLUGIN_INFO = { C.PI_NAME: "Stanza Forwarding", C.PI_IMPORT_NAME: "XEP-0297", C.PI_TYPE: "XEP", C.PI_PROTOCOLS: ["XEP-0297"], + C.PI_DEPENDENCIES: ["XEP-0428"], C.PI_MAIN: "XEP_0297", C.PI_HANDLER: "yes", C.PI_DESCRIPTION: D_("""Implementation of Stanza Forwarding"""), } +NS_FORWARD = "urn:xmpp:forward:0" -class XEP_0297(object): - # FIXME: check this implementation which doesn't seems to be used + +class XEP_0297: + # TODO: Handle forwarded message reception, for now the fallback will be used. + # TODO: Add a method to forward Pubsub Item, and notably blog content. def __init__(self, host): log.info(_("Stanza Forwarding plugin initialization")) self.host = host + self._fallback = cast(XEP_0428, host.plugins["XEP-0428"]) + host.register_namespace("forward", NS_FORWARD) + host.bridge.add_method( + "message_forward", + ".plugin", + in_sign="sss", + out_sign="", + method=self._forward, + async_=True, + ) def get_handler(self, client): return XEP_0297_handler(self, client.profile) @@ -72,41 +96,102 @@ if isinstance(child, domish.Element) and not child.uri: XEP_0297.update_uri(child, uri) - def forward(self, stanza, to_jid, stamp, body="", profile_key=C.PROF_KEY_NONE): + def _forward( + self, + message_id: str, + recipient_jid_s: str, + profile_key: str + ) -> defer.Deferred[None]: + client = self.host.get_client(profile_key) + recipient_jid = jid.JID(recipient_jid_s) + return defer.ensureDeferred(self.forward_by_id(client, message_id, recipient_jid)) + + async def forward_by_id( + self, + client: SatXMPPEntity, + message_id: str, + recipient_jid: jid.JID + ) -> None: + history = cast(History, await G.storage.get( + client, + History, + History.uid, + message_id, + joined_loads=[History.messages, History.subjects, History.thread], + )) + if not history: + raise exceptions.NotFound( + f"No history found with message {message_id!r}." + ) + # FIXME: Q&D way to get MessageData from History. History should have a proper + # way to do that. + serialised_history = history.serialise() + serialised_history["from"] = jid.JID(serialised_history["from"]) + serialised_history["to"] = jid.JID(serialised_history["to"]) + mess_data = MessageData(serialised_history) + client.generate_message_xml(mess_data) + message_elt = cast(domish.Element, mess_data["xml"]) + timestamp_float = float(history.timestamp or history.received_timestamp) + timestamp = datetime.fromtimestamp(timestamp_float, tz.tzutc()) + + fallback_lines = [ + f"{client.jid.userhost()} is forwarding this message to you:", + f"[{date_fmt(timestamp_float)}]", + f"From: {history.source_jid.full()}", + f"To: {history.dest_jid.full()}" + ] + + for subject in history.subjects: + if subject.language: + fallback_lines.append(f"Subject [{subject.language}]: {subject.subject}") + else: + fallback_lines.append(f"Subject: {subject.subject}") + + for message in history.messages: + if message.language: + fallback_lines.append(f"Message [{message.language}]: {message.message}") + else: + fallback_lines.append(f"Message: {message.message}") + + fallback_msg = "\n".join(fallback_lines) + + await self.forward(client, message_elt, recipient_jid, timestamp, fallback_msg) + + async def forward( + self, + client: SatXMPPEntity, + stanza: domish.Element, + to_jid: jid.JID, + timestamp: datetime|None, + fallback_msg: str | None = None + ): """Forward a message to the given JID. - @param stanza (domish.Element): original stanza to be forwarded. - @param to_jid (JID): recipient JID. - @param stamp (datetime): offset-aware timestamp of the original reception. - @param body (unicode): optional description. - @param profile_key (unicode): %(doc_profile_key)s + @param client: client instance. + @param stanza: original stanza to be forwarded. + @param to_jid: recipient JID. + @param timestamp: offset-aware timestamp of the original reception. + @param body: optional description. @return: a Deferred when the message has been sent """ - # FIXME: this method is not used and doesn't use mess_data which should be used for client.send_message_data - # should it be deprecated? A method constructing the element without sending it seems more natural - log.warning( - "THIS METHOD IS DEPRECATED" - ) # Â FIXME: we use this warning until we check the method - msg = domish.Element((None, "message")) - msg["to"] = to_jid.full() - msg["type"] = stanza["type"] - - body_elt = domish.Element((None, "body")) - if body: - body_elt.addContent(body) + message_elt = domish.Element((None, "message")) + message_elt["to"] = to_jid.full() + message_elt["type"] = stanza["type"] forwarded_elt = domish.Element((C.NS_FORWARD, "forwarded")) - delay_elt = self.host.plugins["XEP-0203"].delay(stamp) - forwarded_elt.addChild(delay_elt) - if not stanza.uri: # None or '' + if timestamp: + delay_elt = self.host.plugins["XEP-0203"].delay(timestamp) + forwarded_elt.addChild(delay_elt) + if not stanza.uri: XEP_0297.update_uri(stanza, "jabber:client") forwarded_elt.addChild(stanza) - msg.addChild(body_elt) - msg.addChild(forwarded_elt) - - client = self.host.get_client(profile_key) - return defer.ensureDeferred(client.send_message_data({"xml": msg})) + message_elt.addChild(domish.Element((None, "body"))) + message_elt.addChild(forwarded_elt) + self._fallback.add_fallback_elt(message_elt, NS_FORWARD, fallback_msg) + return await client.send_message_data( + MessageData({"xml": message_elt, "extra": {}}) + ) @implementer(iwokkel.IDisco)